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

Commit 6704b62

Browse files
committed
fix snippet scope issues
1 parent 15d9ead commit 6704b62

File tree

6 files changed

+264
-68
lines changed

6 files changed

+264
-68
lines changed

source/workspaced/com/snippets/package.d

Lines changed: 105 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,46 @@ class SnippetsComponent : ComponentWrapper
7070
scope tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
7171
auto loc = tokens.tokenIndexAtByteIndex(position);
7272

73-
// nudge in next token if position is not exactly on the start of it
74-
if (loc < tokens.length && tokens[loc].index < position)
75-
loc++;
76-
// determine info from before start of identifier (so you can start typing something and it still finds a snippet scope)
77-
if (loc > 0 && loc < tokens.length && tokens[loc].type == tok!"identifier" && tokens[loc].index >= position)
73+
// first check if at end of identifier, move current location to that
74+
// identifier.
75+
if (loc > 0
76+
&& loc < tokens.length
77+
&& tokens[loc - 1].isLikeIdentifier
78+
&& tokens[loc - 1].index <= position
79+
&& tokens[loc - 1].index + tokens[loc - 1].textLength >= position)
80+
loc--;
81+
// also determine info from before start of identifier (so you can start
82+
// typing something and it still finds a snippet scope)
83+
// > double decrement when at end of identifier, start of other token!
84+
if (loc > 0
85+
&& loc < tokens.length
86+
&& tokens[loc].isLikeIdentifier
87+
&& tokens[loc].index <= position
88+
&& tokens[loc].index + tokens[loc].textLength >= position)
7889
loc--;
7990

91+
// nudge in next token if position is after this token
92+
if (loc < tokens.length && tokens[loc].isLikeIdentifier
93+
&& position > tokens[loc].index + tokens[loc].textLength)
94+
{
95+
// cursor must not be glued to the end of identifiers
96+
loc++;
97+
}
98+
else if (loc < tokens.length && !tokens[loc].isLikeIdentifier
99+
&& position >= tokens[loc].index + tokens[loc].textLength)
100+
{
101+
// but next token if end of non-identifiers (eg `""`, `;`, `.`, `(`)
102+
loc++;
103+
}
104+
80105
int contextIndex;
106+
int checkLocation = position;
81107
if (loc >= 0 && loc < tokens.length)
108+
{
82109
contextIndex = cast(int) tokens[loc].index;
110+
if (tokens[loc].index < position)
111+
checkLocation = contextIndex;
112+
}
83113

84114
if (loc == 0 || loc == tokens.length)
85115
return SnippetInfo(contextIndex, [SnippetLevel.global]);
@@ -91,47 +121,88 @@ class SnippetsComponent : ComponentWrapper
91121
auto last = leading[$ - 1];
92122
switch (last.type)
93123
{
94-
case tok!"comment":
95-
size_t len = max(0, cast(ptrdiff_t)position
96-
- cast(ptrdiff_t)last.index);
97-
// TODO: currently never called because we would either need to
98-
// use the DLexer struct as parser immediately or wait until
99-
// libdparse >=0.15.0 which contains trivia, where this switch
100-
// needs to be modified to check the exact trivia token instead
101-
// of the associated token with it.
102-
if (last.text[0 .. len].startsWith("///", "/++", "/**"))
103-
return SnippetInfo(contextIndex, [SnippetLevel.docComment]);
104-
else if (len >= 2)
105-
return SnippetInfo(contextIndex, [SnippetLevel.comment]);
106-
else
107-
break;
124+
case tok!".":
125+
case tok!")":
126+
case tok!"characterLiteral":
108127
case tok!"dstringLiteral":
109128
case tok!"wstringLiteral":
110129
case tok!"stringLiteral":
111-
if (position <= last.index)
112-
break;
113-
114-
auto textSoFar = last.text[1 .. position - last.index];
115-
// no string complete if we are immediately after escape or
116-
// quote character
117-
// TODO: properly check if this is an unescaped escape
118-
if (textSoFar.endsWith('\\', last.text[0]))
119-
return SnippetInfo(contextIndex, [SnippetLevel.strings, SnippetLevel.other]);
120-
else
121-
return SnippetInfo(contextIndex, [SnippetLevel.strings]);
130+
// no snippets immediately after these tokens (needs some other
131+
// token inbetween)
132+
return SnippetInfo(contextIndex, [SnippetLevel.other]);
122133
case tok!"(":
134+
// current token is something like `)`, check for previous
135+
// tokens like `__traits` `(`
123136
if (leading.length >= 2)
124137
{
125-
auto beforeLast = leading[$ - 2];
126-
if (beforeLast.type.among(tok!"__traits", tok!"version", tok!"debug"))
138+
switch (leading[$ - 2].type)
139+
{
140+
case tok!"__traits":
141+
case tok!"version":
142+
case tok!"debug":
127143
return SnippetInfo(contextIndex, [SnippetLevel.other]);
144+
default: break;
145+
}
128146
}
129147
break;
148+
case tok!"__traits":
149+
case tok!"version":
150+
case tok!"debug":
151+
return SnippetInfo(contextIndex, [SnippetLevel.other]);
152+
case tok!"typeof":
153+
case tok!"if":
154+
case tok!"while":
155+
case tok!"for":
156+
case tok!"foreach":
157+
case tok!"foreach_reverse":
158+
case tok!"switch":
159+
case tok!"with":
160+
case tok!"catch":
161+
// immediately after these tokens, missing opening parentheses
162+
if (tokens[loc].type != tok!"(")
163+
return SnippetInfo(contextIndex, [SnippetLevel.other]);
164+
break;
130165
default:
131166
break;
132167
}
133168
}
134169

170+
auto current = tokens[loc];
171+
switch (current.type)
172+
{
173+
case tok!"comment":
174+
size_t len = max(0, cast(ptrdiff_t)position
175+
- cast(ptrdiff_t)current.index);
176+
// TODO: currently never called because we would either need to
177+
// use the DLexer struct as parser immediately or wait until
178+
// libdparse >=0.15.0 which contains trivia, where this switch
179+
// needs to be modified to check the exact trivia token instead
180+
// of the associated token with it.
181+
if (current.text[0 .. len].startsWith("///", "/++", "/**"))
182+
return SnippetInfo(contextIndex, [SnippetLevel.docComment]);
183+
else if (len >= 2)
184+
return SnippetInfo(contextIndex, [SnippetLevel.comment]);
185+
else
186+
break;
187+
case tok!"characterLiteral":
188+
case tok!"dstringLiteral":
189+
case tok!"wstringLiteral":
190+
case tok!"stringLiteral":
191+
if (position <= current.index)
192+
break;
193+
194+
auto textSoFar = current.text[1 .. position - current.index];
195+
// no string complete if we are immediately after escape or
196+
// quote character
197+
// TODO: properly check if this is an unescaped escape
198+
if (textSoFar.endsWith('\\', current.text[0]))
199+
return SnippetInfo(contextIndex, [SnippetLevel.strings, SnippetLevel.other]);
200+
else
201+
return SnippetInfo(contextIndex, [SnippetLevel.strings]);
202+
default:
203+
break;
204+
}
205+
135206
foreach_reverse (t; leading)
136207
{
137208
if (t.type == tok!";")
@@ -153,9 +224,9 @@ class SnippetsComponent : ComponentWrapper
153224
RollbackAllocator rba;
154225
scope parsed = parseModule(tokens, cast(string) file, &rba);
155226

156-
//trace("determineSnippetInfo at ", position);
227+
//trace("determineSnippetInfo at ", contextIndex);
157228

158-
scope gen = new SnippetInfoGenerator(position);
229+
scope gen = new SnippetInfoGenerator(checkLocation);
159230
gen.value.contextTokenIndex = contextIndex;
160231
gen.variableStack.reserve(64);
161232
gen.visit(parsed);

source/workspaced/dparseext.d

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,25 @@ string tokenText(const Token token)
7171
}
7272
}
7373

74+
size_t textLength(const Token token)
75+
{
76+
return token.tokenText.length;
77+
}
78+
79+
bool isSomeString(const Token token)
80+
{
81+
switch (token.type)
82+
{
83+
case tok!"characterLiteral":
84+
case tok!"dstringLiteral":
85+
case tok!"stringLiteral":
86+
case tok!"wstringLiteral":
87+
return true;
88+
default:
89+
return false;
90+
}
91+
}
92+
7493
bool isLikeIdentifier(const Token token)
7594
{
7695
import workspaced.helpers;

source/workspaced/helpers.d

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -78,56 +78,72 @@ version (unittest)
7878
void delegate() onFileStart,
7979
void delegate(string code, string variable, JSONValue value) setVariable,
8080
void delegate(string code, string[] parts, string line) onTestLine,
81-
void delegate(string code) onFileFinished)
81+
void delegate(string code) onFileFinished,
82+
string __file = __FILE__,
83+
size_t __line = __LINE__)
8284
{
85+
import core.exception;
8386
import std.algorithm;
8487
import std.array;
88+
import std.conv;
8589
import std.file;
8690
import std.stdio;
8791

8892
int noTested = 0;
8993
foreach (testFile; dirEntries(dir, SpanMode.shallow))
9094
{
91-
auto testCode = appender!string;
92-
bool inCode = true;
93-
if (onFileStart)
94-
onFileStart();
95-
foreach (line; File(testFile, "r").byLine)
95+
int lineNo = 0;
96+
try
9697
{
97-
if (line == "__EOF__")
98+
auto testCode = appender!string;
99+
bool inCode = true;
100+
if (onFileStart)
101+
onFileStart();
102+
foreach (line; File(testFile, "r").byLine)
98103
{
99-
inCode = false;
100-
continue;
101-
}
104+
lineNo++;
105+
if (line == "__EOF__")
106+
{
107+
inCode = false;
108+
continue;
109+
}
102110

103-
if (inCode)
104-
{
105-
testCode ~= line;
106-
testCode ~= '\n'; // normalize CRLF to LF
107-
}
108-
else if (!line.length || line.startsWith("//"))
109-
{
110-
continue;
111-
}
112-
else if (line[0] == ':')
113-
{
114-
auto variable = line[1 .. $].idup.findSplit("=");
115-
if (setVariable)
116-
setVariable(testCode.data, variable[0], parseJSON(variable[2]));
117-
}
118-
else
119-
{
120-
if (onTestLine)
111+
if (inCode)
121112
{
122-
string lineDup = line.idup;
123-
onTestLine(testCode.data, lineDup.split("\t"), lineDup);
113+
testCode ~= line;
114+
testCode ~= '\n'; // normalize CRLF to LF
115+
}
116+
else if (!line.length || line.startsWith("//"))
117+
{
118+
continue;
119+
}
120+
else if (line[0] == ':')
121+
{
122+
auto variable = line[1 .. $].idup.findSplit("=");
123+
if (setVariable)
124+
setVariable(testCode.data, variable[0], parseJSON(variable[2]));
125+
}
126+
else
127+
{
128+
if (onTestLine)
129+
{
130+
string lineDup = line.idup;
131+
onTestLine(testCode.data, lineDup.split("\t"), lineDup);
132+
}
124133
}
125134
}
126-
}
127135

128-
if (onFileFinished)
129-
onFileFinished(testCode.data);
130-
noTested++;
136+
if (onFileFinished)
137+
onFileFinished(testCode.data);
138+
noTested++;
139+
}
140+
catch (AssertError e)
141+
{
142+
e.file = __file;
143+
e.line = __line;
144+
e.msg = "in " ~ testFile ~ "(" ~ lineNo.to!string ~ "): " ~ e.msg;
145+
throw e;
146+
}
131147
}
132148

133149
assert(noTested > 0);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
void main()
2+
{
3+
foo.
4+
5+
bar();
6+
}
7+
8+
void main()
9+
{
10+
foo.bar
11+
12+
bar();
13+
}
14+
15+
__EOF__
16+
19 other
17+
51 other
18+
52 other
19+
54 other
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// ugly indents in this file to make sure there is whitespace at these locations
2+
3+
void main()
4+
{
5+
foo();
6+
7+
bar();
8+
}
9+
10+
void foo()
11+
{
12+
bar();
13+
14+
}
15+
16+
void bar()
17+
{
18+
}
19+
20+
void main()
21+
{
22+
foo();
23+
24+
half
25+
26+
bar();
27+
}
28+
29+
void main()
30+
{
31+
foo();
32+
33+
while
34+
35+
bar();
36+
}
37+
38+
__EOF__
39+
106 method
40+
140 method
41+
158 method
42+
186 method
43+
188 method
44+
190 method
45+
191 other
46+
227 method
47+
230 method
48+
232 method
49+
233 other
50+
51+
101 value
52+
102 other

0 commit comments

Comments
 (0)