@@ -73,9 +73,16 @@ class SnippetsComponent : ComponentWrapper
7373 // nudge in next token if position is not exactly on the start of it
7474 if (loc < tokens.length && tokens[loc].index < position)
7575 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)
78+ loc-- ;
79+
80+ int contextIndex;
81+ if (loc >= 0 && loc < tokens.length)
82+ contextIndex = cast (int ) tokens[loc].index;
7683
7784 if (loc == 0 || loc == tokens.length)
78- return SnippetInfo ([SnippetLevel.global]);
85+ return SnippetInfo (contextIndex, [SnippetLevel.global]);
7986
8087 auto leading = tokens[0 .. loc];
8188
@@ -93,9 +100,9 @@ class SnippetsComponent : ComponentWrapper
93100 // needs to be modified to check the exact trivia token instead
94101 // of the associated token with it.
95102 if (last.text[0 .. len].startsWith(" ///" , " /++" , " /**" ))
96- return SnippetInfo ([SnippetLevel.docComment]);
103+ return SnippetInfo (contextIndex, [SnippetLevel.docComment]);
97104 else if (len >= 2 )
98- return SnippetInfo ([SnippetLevel.comment]);
105+ return SnippetInfo (contextIndex, [SnippetLevel.comment]);
99106 else
100107 break ;
101108 case tok! " dstringLiteral" :
@@ -109,15 +116,15 @@ class SnippetsComponent : ComponentWrapper
109116 // quote character
110117 // TODO: properly check if this is an unescaped escape
111118 if (textSoFar.endsWith(' \\ ' , last.text[0 ]))
112- return SnippetInfo ([SnippetLevel.strings, SnippetLevel.other]);
119+ return SnippetInfo (contextIndex, [SnippetLevel.strings, SnippetLevel.other]);
113120 else
114- return SnippetInfo ([SnippetLevel.strings]);
121+ return SnippetInfo (contextIndex, [SnippetLevel.strings]);
115122 case tok! " (" :
116123 if (leading.length >= 2 )
117124 {
118125 auto beforeLast = leading[$ - 2 ];
119126 if (beforeLast.type.among(tok! " __traits" , tok! " version" , tok! " debug" ))
120- return SnippetInfo ([SnippetLevel.other]);
127+ return SnippetInfo (contextIndex, [SnippetLevel.other]);
121128 }
122129 break ;
123130 default :
@@ -133,13 +140,13 @@ class SnippetsComponent : ComponentWrapper
133140 // test for tokens semicolon closed statements where we should abort to avoid incomplete syntax
134141 if (t.type.among! (tok! " import" , tok! " module" ))
135142 {
136- return SnippetInfo ([SnippetLevel.global, SnippetLevel.other]);
143+ return SnippetInfo (contextIndex, [SnippetLevel.global, SnippetLevel.other]);
137144 }
138145 else if (t.type.among! (tok! " =" , tok! " +" , tok! " -" , tok! " *" , tok! " /" ,
139146 tok! " %" , tok! " ^^" , tok! " &" , tok! " |" , tok! " ^" , tok! " <<" ,
140147 tok! " >>" , tok! " >>>" , tok! " ~" , tok! " in" ))
141148 {
142- return SnippetInfo ([SnippetLevel.global, SnippetLevel.value]);
149+ return SnippetInfo (contextIndex, [SnippetLevel.global, SnippetLevel.value]);
143150 }
144151 }
145152
@@ -149,6 +156,7 @@ class SnippetsComponent : ComponentWrapper
149156 // trace("determineSnippetInfo at ", position);
150157
151158 scope gen = new SnippetInfoGenerator(position);
159+ gen.value.contextTokenIndex = contextIndex;
152160 gen.variableStack.reserve (64 );
153161 gen.visit(parsed);
154162
@@ -165,6 +173,34 @@ class SnippetsComponent : ComponentWrapper
165173 }
166174 }
167175
176+ if (gen.lastStatement)
177+ {
178+ import dparse.ast;
179+
180+ LastStatementInfo info;
181+ auto nodeType = gen.lastStatement.findDeepestNonBlockNode;
182+ if (gen.lastStatement.tokens.length)
183+ info.location = cast (int ) nodeType.tokens[0 ].index;
184+ info.type = typeid (nodeType).name;
185+ auto lastDot = info.type.lastIndexOf(' .' );
186+ if (lastDot != - 1 )
187+ info.type = info.type[lastDot + 1 .. $];
188+ if (auto ifStmt = cast (IfStatement)nodeType)
189+ {
190+ auto elseStmt = getIfElse(ifStmt);
191+ if (cast (IfStatement)elseStmt)
192+ info.ifHasElse = false ;
193+ else
194+ info.ifHasElse = elseStmt ! is null ;
195+ }
196+ else if (auto ifStmt = cast (ConditionalDeclaration)nodeType)
197+ info.ifHasElse = ifStmt.hasElse;
198+ // if (auto ifStmt = cast(ConditionalStatement)nodeType)
199+ // info.ifHasElse = !!getIfElse(ifStmt);
200+
201+ gen.value.lastStatement = info;
202+ }
203+
168204 return gen.value;
169205 }
170206
@@ -540,10 +576,15 @@ struct SnippetLoopScope
540576// /
541577struct SnippetInfo
542578{
579+ // / Index in code which token was used to determine this snippet info.
580+ int contextTokenIndex;
543581 // / Levels this snippet location has gone through, latest one being the last
544582 SnippetLevel[] stack = [SnippetLevel.global];
545583 // / Information about snippets using loop context
546584 SnippetLoopScope loopScope;
585+ // / Information about the last parsable statement before the cursor. May be
586+ // / `LastStatementInfo.init` at start of function or block.
587+ LastStatementInfo lastStatement;
547588
548589 // / Current snippet scope level of the location
549590 SnippetLevel level () const @property
@@ -552,6 +593,19 @@ struct SnippetInfo
552593 }
553594}
554595
596+ struct LastStatementInfo
597+ {
598+ // / The libdparse class name (typeid) of the last parsable statement before
599+ // / the cursor, stripped of module name.
600+ string type;
601+ // / If type is set, this is the start location in bytes where
602+ // / the first token was.
603+ int location;
604+ // / True if the type is (`IfStatement`, `ConditionalDeclaration` or
605+ // / `ConditionalStatement`) and has a final `else` block defined.
606+ bool ifHasElse;
607+ }
608+
555609// / A list of snippets resolved at a given position.
556610struct SnippetList
557611{
0 commit comments