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 : Position, Range, 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(const Uri uri, const FormattingOptions options) 82 { 83 import dfmt.formatter : format; 84 import dls.protocol.logger : logger; 85 import dls.util.document : Document; 86 import std.outbuffer : OutBuffer; 87 88 logger.info("Formatting %s", uri.path); 89 90 const document = Document.get(uri); 91 auto contents = cast(ubyte[]) document.toString(); 92 auto config = getFormatConfig(uri, options); 93 auto buffer = new OutBuffer(); 94 format(uri.path, contents, buffer, &config); 95 return diff(uri, buffer.toString()); 96 } 97 98 TextEdit[] rangeFormatting(const Uri uri, const Range range, const FormattingOptions options) 99 { 100 import dls.util.document : Document; 101 import std.algorithm : filter; 102 import std.array : array; 103 104 const document = Document.get(uri); 105 document.validatePosition(range.start); 106 document.validatePosition(range.end); 107 108 return formatting(uri, options).filter!((edit) { 109 return document.byteAtPosition(edit.range.start) < document.byteAtPosition(range.end) 110 && document.byteAtPosition(edit.range.end) > document.byteAtPosition(range.start); 111 }).array; 112 } 113 114 TextEdit[] onTypeFormatting(const Uri uri, const Position position, 115 const FormattingOptions options) 116 { 117 import dls.util.document : Document; 118 import std.algorithm : filter; 119 import std.array : array; 120 import std..string : stripRight; 121 122 const document = Document.get(uri); 123 document.validatePosition(position); 124 125 if (position.character != stripRight(document.lines[position.line]).length) 126 { 127 return []; 128 } 129 130 return formatting(uri, options).filter!(edit => edit.range.start.line == position.line 131 || edit.range.end.line == position.line).array; 132 } 133 134 private Config getFormatConfig(const Uri uri, const FormattingOptions options) 135 { 136 import dfmt.editorconfig : IndentStyle, OptionalBoolean, getConfigFor; 137 import dls.tools.symbol_tool : SymbolTool; 138 139 static OptionalBoolean toOptBool(bool b) 140 { 141 return b ? OptionalBoolean.t : OptionalBoolean.f; 142 } 143 144 auto conf = getConfig(SymbolTool.instance.getWorkspace(uri)); 145 Config config; 146 config.initializeWithDefaults(); 147 config.pattern = configPattern; 148 config.end_of_line = eolMap[conf.format.endOfLine]; 149 config.indent_style = options.insertSpaces ? IndentStyle.space : IndentStyle.tab; 150 config.indent_size = cast(typeof(config.indent_size)) options.tabSize; 151 config.tab_width = config.indent_size; 152 config.max_line_length = conf.format.maxLineLength; 153 config.dfmt_align_switch_statements = toOptBool(conf.format.dfmtAlignSwitchStatements); 154 config.dfmt_brace_style = braceStyleMap[conf.format.dfmtBraceStyle]; 155 config.dfmt_outdent_attributes = toOptBool(conf.format.dfmtOutdentAttributes); 156 config.dfmt_soft_max_line_length = conf.format.dfmtSoftMaxLineLength; 157 config.dfmt_space_after_cast = toOptBool(conf.format.dfmtSpaceAfterCast); 158 config.dfmt_space_after_keywords = toOptBool(conf.format.dfmtSpaceAfterKeywords); 159 config.dfmt_space_before_function_parameters = toOptBool( 160 conf.format.dfmtSpaceBeforeFunctionParameters); 161 config.dfmt_split_operator_at_line_end = toOptBool(conf.format.dfmtSplitOperatorAtLineEnd); 162 config.dfmt_selective_import_space = toOptBool(conf.format.dfmtSelectiveImportSpace); 163 config.dfmt_compact_labeled_statements = conf.format.dfmtCompactLabeledStatements 164 ? OptionalBoolean.t : OptionalBoolean.f; 165 config.dfmt_template_constraint_style 166 = templateConstraintStyleMap[conf.format.dfmtTemplateConstraintStyle]; 167 config.dfmt_single_template_constraint_indent = toOptBool( 168 conf.format.dfmtSingleTemplateConstraintIndent); 169 170 auto fileConfig = getConfigFor!Config(uri.path); 171 fileConfig.pattern = configPattern; 172 config.merge(fileConfig, configPattern); 173 return config; 174 } 175 176 private TextEdit[] diff(const Uri uri, const string after) 177 { 178 import dls.util.document : Document; 179 import std.ascii : isWhite; 180 import std.utf : decode; 181 182 const document = Document.get(uri); 183 immutable before = document.toString(); 184 size_t i; 185 size_t j; 186 TextEdit[] result; 187 188 size_t startIndex; 189 size_t stopIndex; 190 string text; 191 192 bool pushTextEdit() 193 { 194 if (startIndex != stopIndex || text.length > 0) 195 { 196 result ~= new TextEdit(new Range(document.positionAtByte(startIndex), 197 document.positionAtByte(stopIndex)), text); 198 return true; 199 } 200 201 return false; 202 } 203 204 while (i < before.length || j < after.length) 205 { 206 auto newI = i; 207 auto newJ = j; 208 dchar beforeChar; 209 dchar afterChar; 210 211 if (newI < before.length) 212 { 213 beforeChar = decode(before, newI); 214 } 215 216 if (newJ < after.length) 217 { 218 afterChar = decode(after, newJ); 219 } 220 221 if (i < before.length && j < after.length && beforeChar == afterChar) 222 { 223 i = newI; 224 j = newJ; 225 226 if (pushTextEdit()) 227 { 228 startIndex = stopIndex; 229 text = ""; 230 } 231 } 232 233 if (startIndex == stopIndex) 234 { 235 startIndex = i; 236 stopIndex = i; 237 } 238 239 auto addition = !isWhite(beforeChar) && isWhite(afterChar); 240 immutable deletion = isWhite(beforeChar) && !isWhite(afterChar); 241 242 if (!addition && !deletion) 243 { 244 addition = before.length - i < after.length - j; 245 } 246 247 if (addition && j < after.length) 248 { 249 text ~= after[j .. newJ]; 250 j = newJ; 251 } 252 else if (i < before.length) 253 { 254 stopIndex = newI; 255 i = newI; 256 } 257 } 258 259 pushTextEdit(); 260 return result; 261 } 262 }