1 /* 2 *Copyright (C) 2018 Laurent Tréguier 3 * 4 *This file is part of DLS. 5 * 6 *DLS is free software: you can redistribute it and/or modify 7 *it under the terms of the GNU General Public License as published by 8 *the Free Software Foundation, either version 3 of the License, or 9 *(at your option) any later version. 10 * 11 *DLS is distributed in the hope that it will be useful, 12 *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 *GNU General Public License for more details. 15 * 16 *You should have received a copy of the GNU General Public License 17 *along with DLS. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 module dls.tools.format_tool; 22 23 import dfmt.config : BraceStyle, TemplateConstraintStyle; 24 import dfmt.editorconfig : EOL; 25 import dls.tools.configuration : Configuration; 26 import dls.tools.tool : Tool; 27 28 private immutable EOL[Configuration.FormatConfiguration.EndOfLine] eolMap; 29 private immutable BraceStyle[Configuration.FormatConfiguration.BraceStyle] braceStyleMap; 30 private immutable TemplateConstraintStyle[Configuration.FormatConfiguration.TemplateConstraintStyle] templateConstraintStyleMap; 31 private immutable configPattern = "dummy.d"; 32 33 shared static this() 34 { 35 //dfmt off 36 eolMap = [ 37 Configuration.FormatConfiguration.EndOfLine.lf : EOL.lf, 38 Configuration.FormatConfiguration.EndOfLine.cr : EOL.cr, 39 Configuration.FormatConfiguration.EndOfLine.crlf : EOL.crlf 40 ]; 41 42 braceStyleMap = [ 43 Configuration.FormatConfiguration.BraceStyle.allman : BraceStyle.allman, 44 Configuration.FormatConfiguration.BraceStyle.otbs : BraceStyle.otbs, 45 Configuration.FormatConfiguration.BraceStyle.stroustrup : BraceStyle.stroustrup 46 ]; 47 48 templateConstraintStyleMap = [ 49 Configuration.FormatConfiguration.TemplateConstraintStyle.conditionalNewlineIndent : TemplateConstraintStyle.conditional_newline_indent, 50 Configuration.FormatConfiguration.TemplateConstraintStyle.conditionalNewline : TemplateConstraintStyle.conditional_newline, 51 Configuration.FormatConfiguration.TemplateConstraintStyle.alwaysNewline : TemplateConstraintStyle.always_newline, 52 Configuration.FormatConfiguration.TemplateConstraintStyle.alwaysNewlineIndent : TemplateConstraintStyle.always_newline_indent 53 ]; 54 //dfmt on 55 } 56 57 class FormatTool : Tool 58 { 59 import dfmt.config : Config; 60 import dls.protocol.definitions : TextEdit; 61 import dls.protocol.interfaces : FormattingOptions; 62 import dls.util.uri : Uri; 63 64 private static FormatTool _instance; 65 66 static void initialize() 67 { 68 _instance = new FormatTool(); 69 } 70 71 static void shutdown() 72 { 73 destroy(_instance); 74 } 75 76 @property static FormatTool instance() 77 { 78 return _instance; 79 } 80 81 TextEdit[] formatting(in Uri uri, in FormattingOptions options) 82 { 83 import dfmt.formatter : format; 84 import dls.protocol.definitions : Position, Range; 85 import dls.util.document : Document; 86 import dls.util.logger : logger; 87 import std.outbuffer : OutBuffer; 88 89 logger.infof("Formatting %s", uri.path); 90 91 const document = Document.get(uri); 92 auto contents = cast(ubyte[]) document.toString(); 93 auto config = getConfig(uri, options); 94 auto buffer = new OutBuffer(); 95 format(uri.path, contents, buffer, &config); 96 return diff(uri, buffer.toString()); 97 } 98 99 private Config getConfig(in Uri uri, in FormattingOptions options) 100 { 101 import dfmt.editorconfig : IndentStyle, OptionalBoolean, getConfigFor; 102 import dls.tools.symbol_tool : SymbolTool; 103 104 static OptionalBoolean toOptBool(bool b) 105 { 106 return b ? OptionalBoolean.t : OptionalBoolean.f; 107 } 108 109 Config config; 110 config.pattern = configPattern; 111 config.initializeWithDefaults(); 112 config.end_of_line = eolMap[configuration.format.endOfLine]; 113 config.indent_style = options.insertSpaces ? IndentStyle.space : IndentStyle.tab; 114 config.indent_size = cast(typeof(config.indent_size)) options.tabSize; 115 config.tab_width = config.indent_size; 116 config.max_line_length = configuration.format.maxLineLength; 117 config.dfmt_align_switch_statements = toOptBool( 118 configuration.format.dfmtAlignSwitchStatements); 119 config.dfmt_brace_style = braceStyleMap[configuration.format.dfmtBraceStyle]; 120 config.dfmt_outdent_attributes = toOptBool(configuration.format.dfmtOutdentAttributes); 121 config.dfmt_soft_max_line_length = configuration.format.dfmtSoftMaxLineLength; 122 config.dfmt_space_after_cast = toOptBool(configuration.format.dfmtSpaceAfterCast); 123 config.dfmt_space_after_keywords = toOptBool(configuration.format.dfmtSpaceAfterKeywords); 124 config.dfmt_space_before_function_parameters = toOptBool( 125 configuration.format.dfmtSpaceBeforeFunctionParameters); 126 config.dfmt_split_operator_at_line_end = toOptBool( 127 configuration.format.dfmtSplitOperatorAtLineEnd); 128 config.dfmt_selective_import_space = toOptBool( 129 configuration.format.dfmtSelectiveImportSpace); 130 config.dfmt_compact_labeled_statements = configuration.format.dfmtCompactLabeledStatements 131 ? OptionalBoolean.t : OptionalBoolean.f; 132 config.dfmt_template_constraint_style 133 = templateConstraintStyleMap[configuration.format.dfmtTemplateConstraintStyle]; 134 config.dfmt_single_template_constraint_indent = toOptBool( 135 configuration.format.dfmtSingleTemplateConstraintIndent); 136 137 auto fileConfig = getConfigFor!Config(SymbolTool.instance.getWorkspace(uri).path); 138 fileConfig.pattern = configPattern; 139 config.merge(fileConfig, configPattern); 140 return config; 141 } 142 143 private TextEdit[] diff(in Uri uri, in string after) 144 { 145 import dls.protocol.definitions : Range; 146 import dls.util.document : Document; 147 import std.ascii : isWhite; 148 import std.utf : decode; 149 150 const document = Document.get(uri); 151 const before = document.toString(); 152 size_t i; 153 size_t j; 154 TextEdit[] result; 155 156 size_t startIndex; 157 size_t stopIndex; 158 string text; 159 160 bool pushTextEdit() 161 { 162 if (startIndex != stopIndex || text.length > 0) 163 { 164 result ~= new TextEdit(new Range(document.positionAtByte(startIndex), 165 document.positionAtByte(stopIndex)), text); 166 return true; 167 } 168 169 return false; 170 } 171 172 while (i < before.length || j < after.length) 173 { 174 auto newI = i; 175 auto newJ = j; 176 dchar beforeChar; 177 dchar afterChar; 178 179 if (newI < before.length) 180 { 181 beforeChar = decode(before, newI); 182 } 183 184 if (newJ < after.length) 185 { 186 afterChar = decode(after, newJ); 187 } 188 189 if (i < before.length && j < after.length && beforeChar == afterChar) 190 { 191 i = newI; 192 j = newJ; 193 194 if (pushTextEdit()) 195 { 196 startIndex = stopIndex; 197 text = ""; 198 } 199 } 200 else 201 { 202 if (startIndex == stopIndex) 203 { 204 startIndex = i; 205 stopIndex = i; 206 } 207 208 if (j < after.length && !isWhite(beforeChar) 209 && (isWhite(afterChar) || before.length - i < after.length - j)) 210 { 211 text ~= after[j .. newJ]; 212 j = newJ; 213 } 214 else if (i < before.length) 215 { 216 stopIndex = newI; 217 i = newI; 218 } 219 } 220 } 221 222 pushTextEdit(); 223 return result; 224 } 225 }