22module workspaced.com.importer ;
33
44import dparse.ast;
5+ import dparse.lexer;
56import dparse.parser;
67import dparse.rollback_allocator;
7- import dparse.lexer;
88
99import std.algorithm ;
1010import std.array ;
1111import std.functional ;
1212import std.stdio ;
1313import std.string ;
14+ import std.uni : sicmp;
1415
1516import workspaced.api;
17+ import workspaced.helpers : determineIndentation, indexOfKeyword, stripLineEndingLength;
1618
1719// / ditto
1820@component(" importer" )
@@ -78,6 +80,7 @@ class ImporterComponent : ComponentWrapper
7880 ImportBlock sortImports (string code, int pos)
7981 {
8082 bool startBlock = true ;
83+ string indentation;
8184 size_t start, end;
8285 // find block of code separated by empty lines
8386 foreach (line; code.lineSplitter! (KeepTerminator.yes))
@@ -89,22 +92,86 @@ class ImporterComponent : ComponentWrapper
8992 break ;
9093 end += line.length;
9194 }
92- if (end > start && end + 1 < code.length)
93- end-- ;
94- if (start >= end || end >= code.length)
95+ if (start >= end || end > code.length)
9596 return ImportBlock.init;
9697 auto part = code[start .. end];
98+
99+ // then filter out the proper indentation
100+ bool inCorrectIndentationBlock;
101+ size_t acc;
102+ bool midImport;
103+ foreach (line; part.lineSplitter! (KeepTerminator.yes))
104+ {
105+ const indent = line.determineIndentation;
106+ bool marksNewRegion;
107+ bool leavingMidImport;
108+
109+ const importStart = line.indexOfKeyword(" import" );
110+ const importEnd = line.indexOf(' ;' );
111+ if (importStart != - 1 )
112+ {
113+ acc += importStart;
114+ line = line[importStart .. $];
115+
116+ if (importEnd == - 1 )
117+ midImport = true ;
118+ else
119+ midImport = importEnd < importStart;
120+ }
121+ else if (importEnd != - 1 && midImport)
122+ leavingMidImport = true ;
123+ else if (! midImport)
124+ {
125+ // got no "import" and wasn't in an import here
126+ marksNewRegion = true ;
127+ }
128+
129+ if ((marksNewRegion || indent != indentation) && ! midImport)
130+ {
131+ if (inCorrectIndentationBlock)
132+ {
133+ end = start + acc - line.stripLineEndingLength;
134+ break ;
135+ }
136+ start += acc;
137+ acc = 0 ;
138+ indentation = indent;
139+ }
140+
141+ if (leavingMidImport)
142+ midImport = false ;
143+
144+ if (start + acc <= pos && start + acc + line.length - 1 >= pos)
145+ inCorrectIndentationBlock = true ;
146+ acc += line.length;
147+ }
148+
149+ part = code[start .. end];
150+
97151 auto tokens = getTokensForParser(cast (ubyte []) part, config, &workspaced.stringCache);
98152 auto mod = parseModule(tokens, " code" , &rba);
99153 auto reader = new ImporterReaderVisitor(- 1 );
100154 reader.visit(mod);
155+
101156 auto imports = reader.imports;
157+ if (! imports.length)
158+ return ImportBlock.init;
159+
160+ foreach (ref imp; imports)
161+ imp.start += start;
162+
163+ start = imports.front.start;
164+ end = code.indexOf(' ;' , imports.back.start) + 1 ;
165+
102166 auto sorted = imports.map! (a => ImportInfo(a.name, a.rename,
103- a.selectives.dup .sort! ((c, d) => icmp(c.effectiveName, d.effectiveName) < 0 ).array)).array.sort! ((a,
104- b) => icmp(a.effectiveName, b.effectiveName) < 0 ).array;
167+ a.selectives.dup .sort! ((c, d) => sicmp(c.effectiveName, d.effectiveName) < 0 ).array,
168+ a.start))
169+ .array
170+ .sort! ((a, b) => sicmp(a.effectiveName, b.effectiveName) < 0 )
171+ .array;
105172 if (sorted == imports)
106173 return ImportBlock.init;
107- return ImportBlock (cast (int ) start, cast (int ) end, sorted);
174+ return ImportBlock (cast (int ) start, cast (int ) end, sorted, indentation );
108175 }
109176
110177private :
@@ -116,9 +183,9 @@ unittest
116183{
117184 import std.conv : to;
118185
119- void assertEqual (A, B)(A a, B b)
186+ void assertEqual (ImportBlock a, ImportBlock b)
120187 {
121- assert (a == b , a.to! string ~ " is not equal to " ~ b.to! string );
188+ assert (a.sameEffectAs(b) , a.to! string ~ " is not equal to " ~ b.to! string );
122189 }
123190
124191 auto backend = new WorkspaceD();
@@ -148,7 +215,37 @@ import sorted;
148215
149216import std.stdio : writeln, File, stdout, err = stderr;
150217
151- void main() {}` ;
218+ version(unittest)
219+ import std.traits;
220+ import std.stdio;
221+ import std.algorithm;
222+
223+ void main()
224+ {
225+ import std.stdio;
226+ import std.algorithm;
227+
228+ writeln("foo");
229+ }
230+
231+ void main()
232+ {
233+ import std.stdio;
234+ import std.algorithm;
235+ }
236+
237+ void main()
238+ {
239+ import std.stdio;
240+ import std.algorithm;
241+ string midImport;
242+ import std.string;
243+ import std.array;
244+ }
245+
246+ import workspaced.api;
247+ import workspaced.helpers : determineIndentation, stripLineEndingLength, indexOfKeyword;
248+ ` ;
152249
153250 // dfmt off
154251 assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 0 ), ImportBlock(0 , 164 , [
@@ -198,6 +295,35 @@ void main() {}`;
198295 SelectiveImport(" writeln" ),
199296 ])
200297 ]));
298+
299+ assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 593 ), ImportBlock(586 , 625 , [
300+ ImportInfo([" std" , " algorithm" ]),
301+ ImportInfo([" std" , " stdio" ])
302+ ]));
303+
304+ assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 650 ), ImportBlock(642 , 682 , [
305+ ImportInfo([" std" , " algorithm" ]),
306+ ImportInfo([" std" , " stdio" ])
307+ ], " \t " ));
308+
309+ assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 730 ), ImportBlock(719 , 759 , [
310+ ImportInfo([" std" , " algorithm" ]),
311+ ImportInfo([" std" , " stdio" ])
312+ ], " \t " ));
313+
314+ assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 850 ), ImportBlock(839 , 876 , [
315+ ImportInfo([" std" , " array" ]),
316+ ImportInfo([" std" , " string" ])
317+ ], " \t " ));
318+
319+ assertEqual(backend.get ! ImporterComponent(workspace.directory).sortImports(code, 897 ), ImportBlock(880 , 991 , [
320+ ImportInfo([" workspaced" , " api" ]),
321+ ImportInfo([" workspaced" , " helpers" ], " " , [
322+ SelectiveImport(" determineIndentation" ),
323+ SelectiveImport(" indexOfKeyword" ),
324+ SelectiveImport(" stripLineEndingLength" )
325+ ])
326+ ]));
201327 // dfmt on
202328}
203329
@@ -240,6 +366,8 @@ struct ImportInfo
240366 string rename;
241367 // / Array of selective imports or empty if the entire module has been imported
242368 SelectiveImport[] selectives;
369+ // / Index where the import starts in code
370+ size_t start;
243371
244372 // / Returns the rename if available, otherwise the name joined with dots
245373 string effectiveName () const
@@ -256,6 +384,11 @@ struct ImportInfo
256384 : " " ) ~ name.join(' .' ) ~ (selectives.length
257385 ? " : " ~ selectives.to! (string []).join(" , " ) : " " ) ~ ' ;' ;
258386 }
387+
388+ bool sameEffectAs (in ImportInfo other) const
389+ {
390+ return name == other.name && rename == other.rename && selectives == other.selectives;
391+ }
259392}
260393
261394// / A block of imports generated by the sort-imports command
@@ -265,6 +398,23 @@ struct ImportBlock
265398 int start, end;
266399 // /
267400 ImportInfo[] imports;
401+ // /
402+ string indentation;
403+
404+ bool sameEffectAs (in ImportBlock other) const
405+ {
406+ if (! (start == other.start && end == other.end && indentation == other.indentation))
407+ return false ;
408+
409+ if (imports.length != other.imports.length)
410+ return false ;
411+
412+ foreach (i; 0 .. imports.length)
413+ if (! imports[i].sameEffectAs(other.imports[i]))
414+ return false ;
415+
416+ return true ;
417+ }
268418}
269419
270420private :
@@ -344,7 +494,7 @@ class ImporterReaderVisitor : ASTVisitor
344494 outerImportLocation = decl.endIndex;
345495 foreach (i; decl.singleImports)
346496 imports ~= ImportInfo(i.identifierChain.identifiers.map! (tok => tok.text.idup)
347- .array, i.rename.text);
497+ .array, i.rename.text, null , decl.tokens[ 0 ].index );
348498 if (decl.importBindings)
349499 {
350500 ImportInfo info;
@@ -360,6 +510,7 @@ class ImporterReaderVisitor : ASTVisitor
360510 else
361511 info.selectives ~= SelectiveImport(bind.left.text);
362512 }
513+ info.start = decl.tokens[0 ].index;
363514 if (info.selectives.length)
364515 imports ~= info;
365516 }
0 commit comments