Skip to content
This repository was archived by the owner on Mar 1, 2022. It is now read-only.

Commit 79f90db

Browse files
committed
much love to import sorting
fix Pure-D/code-d#231
1 parent 2c4d187 commit 79f90db

3 files changed

Lines changed: 216 additions & 23 deletions

File tree

source/workspaced/com/importer.d

Lines changed: 162 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@
22
module workspaced.com.importer;
33

44
import dparse.ast;
5+
import dparse.lexer;
56
import dparse.parser;
67
import dparse.rollback_allocator;
7-
import dparse.lexer;
88

99
import std.algorithm;
1010
import std.array;
1111
import std.functional;
1212
import std.stdio;
1313
import std.string;
14+
import std.uni : sicmp;
1415

1516
import 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

110177
private:
@@ -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
149216
import 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

270420
private:
@@ -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
}

source/workspaced/helpers.d

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
module workspaced.helpers;
2+
3+
import std.ascii;
4+
import std.string;
5+
6+
string determineIndentation(string code)
7+
{
8+
string indent = null;
9+
foreach (line; code.lineSplitter)
10+
{
11+
if (line.strip.length == 0)
12+
continue;
13+
indent = line[0 .. $ - line.stripLeft.length];
14+
}
15+
return indent;
16+
}
17+
18+
int stripLineEndingLength(string code)
19+
{
20+
if (code.endsWith("\r\n"))
21+
return 2;
22+
else if (code.endsWith("\r", "\n"))
23+
return 1;
24+
else
25+
return 0;
26+
}
27+
28+
bool isIdentifierChar(dchar c)
29+
{
30+
return c.isAlphaNum || c == '_';
31+
}
32+
33+
ptrdiff_t indexOfKeyword(string code, string keyword, ptrdiff_t start = 0)
34+
{
35+
ptrdiff_t index = start;
36+
while (true)
37+
{
38+
index = code.indexOf(keyword, index);
39+
if (index == -1)
40+
break;
41+
42+
if ((index > 0 && code[index - 1].isIdentifierChar)
43+
|| (index + keyword.length < code.length && code[index + keyword.length].isIdentifierChar))
44+
{
45+
index++;
46+
continue;
47+
}
48+
else
49+
break;
50+
}
51+
return index;
52+
}

source/workspaced/visitors/classifier.d

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module workspaced.visitors.classifier;
33

44
import workspaced.visitors.attributes;
55

6+
import workspaced.helpers : determineIndentation;
7+
68
import workspaced.com.dcdext;
79

810
import std.algorithm;
@@ -222,18 +224,6 @@ class CodeDefinitionClassifier : AttributesVisitor
222224
uint[2] currentRange;
223225
}
224226

225-
private string determineIndentation(string code)
226-
{
227-
string indent = null;
228-
foreach (line; code.lineSplitter)
229-
{
230-
if (line.strip.length == 0)
231-
continue;
232-
indent = line[0 .. $ - line.stripLeft.length];
233-
}
234-
return indent;
235-
}
236-
237227
private int scoreIndent(string indent)
238228
{
239229
auto len = indent.countUntil!(a => !a.isWhite);

0 commit comments

Comments
 (0)