@@ -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);
0 commit comments