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

Commit

Permalink
fix snippet scope issues
Browse files Browse the repository at this point in the history
  • Loading branch information
WebFreak001 committed Nov 20, 2021
1 parent 15d9ead commit 6704b62
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 68 deletions.
139 changes: 105 additions & 34 deletions source/workspaced/com/snippets/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,46 @@ class SnippetsComponent : ComponentWrapper
scope tokens = getTokensForParser(cast(ubyte[]) code, config, &workspaced.stringCache);
auto loc = tokens.tokenIndexAtByteIndex(position);

// nudge in next token if position is not exactly on the start of it
if (loc < tokens.length && tokens[loc].index < position)
loc++;
// determine info from before start of identifier (so you can start typing something and it still finds a snippet scope)
if (loc > 0 && loc < tokens.length && tokens[loc].type == tok!"identifier" && tokens[loc].index >= position)
// first check if at end of identifier, move current location to that
// identifier.
if (loc > 0
&& loc < tokens.length
&& tokens[loc - 1].isLikeIdentifier
&& tokens[loc - 1].index <= position
&& tokens[loc - 1].index + tokens[loc - 1].textLength >= position)
loc--;
// also determine info from before start of identifier (so you can start
// typing something and it still finds a snippet scope)
// > double decrement when at end of identifier, start of other token!
if (loc > 0
&& loc < tokens.length
&& tokens[loc].isLikeIdentifier
&& tokens[loc].index <= position
&& tokens[loc].index + tokens[loc].textLength >= position)
loc--;

// nudge in next token if position is after this token
if (loc < tokens.length && tokens[loc].isLikeIdentifier
&& position > tokens[loc].index + tokens[loc].textLength)
{
// cursor must not be glued to the end of identifiers
loc++;
}
else if (loc < tokens.length && !tokens[loc].isLikeIdentifier
&& position >= tokens[loc].index + tokens[loc].textLength)
{
// but next token if end of non-identifiers (eg `""`, `;`, `.`, `(`)
loc++;
}

int contextIndex;
int checkLocation = position;
if (loc >= 0 && loc < tokens.length)
{
contextIndex = cast(int) tokens[loc].index;
if (tokens[loc].index < position)
checkLocation = contextIndex;
}

if (loc == 0 || loc == tokens.length)
return SnippetInfo(contextIndex, [SnippetLevel.global]);
Expand All @@ -91,47 +121,88 @@ class SnippetsComponent : ComponentWrapper
auto last = leading[$ - 1];
switch (last.type)
{
case tok!"comment":
size_t len = max(0, cast(ptrdiff_t)position
- cast(ptrdiff_t)last.index);
// TODO: currently never called because we would either need to
// use the DLexer struct as parser immediately or wait until
// libdparse >=0.15.0 which contains trivia, where this switch
// needs to be modified to check the exact trivia token instead
// of the associated token with it.
if (last.text[0 .. len].startsWith("///", "/++", "/**"))
return SnippetInfo(contextIndex, [SnippetLevel.docComment]);
else if (len >= 2)
return SnippetInfo(contextIndex, [SnippetLevel.comment]);
else
break;
case tok!".":
case tok!")":
case tok!"characterLiteral":
case tok!"dstringLiteral":
case tok!"wstringLiteral":
case tok!"stringLiteral":
if (position <= last.index)
break;

auto textSoFar = last.text[1 .. position - last.index];
// no string complete if we are immediately after escape or
// quote character
// TODO: properly check if this is an unescaped escape
if (textSoFar.endsWith('\\', last.text[0]))
return SnippetInfo(contextIndex, [SnippetLevel.strings, SnippetLevel.other]);
else
return SnippetInfo(contextIndex, [SnippetLevel.strings]);
// no snippets immediately after these tokens (needs some other
// token inbetween)
return SnippetInfo(contextIndex, [SnippetLevel.other]);
case tok!"(":
// current token is something like `)`, check for previous
// tokens like `__traits` `(`
if (leading.length >= 2)
{
auto beforeLast = leading[$ - 2];
if (beforeLast.type.among(tok!"__traits", tok!"version", tok!"debug"))
switch (leading[$ - 2].type)
{
case tok!"__traits":
case tok!"version":
case tok!"debug":
return SnippetInfo(contextIndex, [SnippetLevel.other]);
default: break;
}
}
break;
case tok!"__traits":
case tok!"version":
case tok!"debug":
return SnippetInfo(contextIndex, [SnippetLevel.other]);
case tok!"typeof":
case tok!"if":
case tok!"while":
case tok!"for":
case tok!"foreach":
case tok!"foreach_reverse":
case tok!"switch":
case tok!"with":
case tok!"catch":
// immediately after these tokens, missing opening parentheses
if (tokens[loc].type != tok!"(")
return SnippetInfo(contextIndex, [SnippetLevel.other]);
break;
default:
break;
}
}

auto current = tokens[loc];
switch (current.type)
{
case tok!"comment":
size_t len = max(0, cast(ptrdiff_t)position
- cast(ptrdiff_t)current.index);
// TODO: currently never called because we would either need to
// use the DLexer struct as parser immediately or wait until
// libdparse >=0.15.0 which contains trivia, where this switch
// needs to be modified to check the exact trivia token instead
// of the associated token with it.
if (current.text[0 .. len].startsWith("///", "/++", "/**"))
return SnippetInfo(contextIndex, [SnippetLevel.docComment]);
else if (len >= 2)
return SnippetInfo(contextIndex, [SnippetLevel.comment]);
else
break;
case tok!"characterLiteral":
case tok!"dstringLiteral":
case tok!"wstringLiteral":
case tok!"stringLiteral":
if (position <= current.index)
break;

auto textSoFar = current.text[1 .. position - current.index];
// no string complete if we are immediately after escape or
// quote character
// TODO: properly check if this is an unescaped escape
if (textSoFar.endsWith('\\', current.text[0]))
return SnippetInfo(contextIndex, [SnippetLevel.strings, SnippetLevel.other]);
else
return SnippetInfo(contextIndex, [SnippetLevel.strings]);
default:
break;
}

foreach_reverse (t; leading)
{
if (t.type == tok!";")
Expand All @@ -153,9 +224,9 @@ class SnippetsComponent : ComponentWrapper
RollbackAllocator rba;
scope parsed = parseModule(tokens, cast(string) file, &rba);

//trace("determineSnippetInfo at ", position);
//trace("determineSnippetInfo at ", contextIndex);

scope gen = new SnippetInfoGenerator(position);
scope gen = new SnippetInfoGenerator(checkLocation);
gen.value.contextTokenIndex = contextIndex;
gen.variableStack.reserve(64);
gen.visit(parsed);
Expand Down
19 changes: 19 additions & 0 deletions source/workspaced/dparseext.d
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ string tokenText(const Token token)
}
}

size_t textLength(const Token token)
{
return token.tokenText.length;
}

bool isSomeString(const Token token)
{
switch (token.type)
{
case tok!"characterLiteral":
case tok!"dstringLiteral":
case tok!"stringLiteral":
case tok!"wstringLiteral":
return true;
default:
return false;
}
}

bool isLikeIdentifier(const Token token)
{
import workspaced.helpers;
Expand Down
84 changes: 50 additions & 34 deletions source/workspaced/helpers.d
Original file line number Diff line number Diff line change
Expand Up @@ -78,56 +78,72 @@ version (unittest)
void delegate() onFileStart,
void delegate(string code, string variable, JSONValue value) setVariable,
void delegate(string code, string[] parts, string line) onTestLine,
void delegate(string code) onFileFinished)
void delegate(string code) onFileFinished,
string __file = __FILE__,
size_t __line = __LINE__)
{
import core.exception;
import std.algorithm;
import std.array;
import std.conv;
import std.file;
import std.stdio;

int noTested = 0;
foreach (testFile; dirEntries(dir, SpanMode.shallow))
{
auto testCode = appender!string;
bool inCode = true;
if (onFileStart)
onFileStart();
foreach (line; File(testFile, "r").byLine)
int lineNo = 0;
try
{
if (line == "__EOF__")
auto testCode = appender!string;
bool inCode = true;
if (onFileStart)
onFileStart();
foreach (line; File(testFile, "r").byLine)
{
inCode = false;
continue;
}
lineNo++;
if (line == "__EOF__")
{
inCode = false;
continue;
}

if (inCode)
{
testCode ~= line;
testCode ~= '\n'; // normalize CRLF to LF
}
else if (!line.length || line.startsWith("//"))
{
continue;
}
else if (line[0] == ':')
{
auto variable = line[1 .. $].idup.findSplit("=");
if (setVariable)
setVariable(testCode.data, variable[0], parseJSON(variable[2]));
}
else
{
if (onTestLine)
if (inCode)
{
string lineDup = line.idup;
onTestLine(testCode.data, lineDup.split("\t"), lineDup);
testCode ~= line;
testCode ~= '\n'; // normalize CRLF to LF
}
else if (!line.length || line.startsWith("//"))
{
continue;
}
else if (line[0] == ':')
{
auto variable = line[1 .. $].idup.findSplit("=");
if (setVariable)
setVariable(testCode.data, variable[0], parseJSON(variable[2]));
}
else
{
if (onTestLine)
{
string lineDup = line.idup;
onTestLine(testCode.data, lineDup.split("\t"), lineDup);
}
}
}
}

if (onFileFinished)
onFileFinished(testCode.data);
noTested++;
if (onFileFinished)
onFileFinished(testCode.data);
noTested++;
}
catch (AssertError e)
{
e.file = __file;
e.line = __line;
e.msg = "in " ~ testFile ~ "(" ~ lineNo.to!string ~ "): " ~ e.msg;
throw e;
}
}

assert(noTested > 0);
Expand Down
19 changes: 19 additions & 0 deletions test/data/snippet_info/dot_exception.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
void main()
{
foo.

bar();
}

void main()
{
foo.bar

bar();
}

__EOF__
19 other
51 other
52 other
54 other
52 changes: 52 additions & 0 deletions test/data/snippet_info/identifiers.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ugly indents in this file to make sure there is whitespace at these locations

void main()
{
foo();

bar();
}

void foo()
{
bar();

}

void bar()
{
}

void main()
{
foo();

half

bar();
}

void main()
{
foo();

while

bar();
}

__EOF__
106 method
140 method
158 method
186 method
188 method
190 method
191 other
227 method
230 method
232 method
233 other

101 value
102 other
Loading

0 comments on commit 6704b62

Please sign in to comment.