@@ -96,174 +96,166 @@ export class NegatableWord implements AbstractWord {
9696}
9797
9898export class AutoCompleteTree {
99- private content : Token [ ] ;
99+ constructor ( private roots : AutoCompleteNode [ ] ) { }
100100
101- constructor ( private roots : AutoCompleteNode [ ] ) {
102- this . content = [ ] ;
103- }
104-
105- /**
106- * Sets the content of the tree for the next analyzing cycle
107- */
108- private setContent ( text : string ) {
109- if ( ! text ) {
110- text = "" ;
111- }
112- if ( text . length == 0 ) {
113- this . content = [ ] ;
114- return ;
101+ private tokenize ( text : string [ ] ) : Token [ ] {
102+ if ( ! text || text . length == 0 ) {
103+ return [ ] ;
115104 }
116105
117- let currentToken = "" ;
118- let currentLine = 1 ;
119- let currentColumn = 0 ;
120- this . content = [ ] ;
121- let index = 0 ;
122- while ( index < text . length ) {
123- const char = text [ index ] ;
124- if ( char === "\n" ) {
125- if ( currentToken . length > 0 ) {
126- this . content . push ( {
127- text : currentToken ,
128- line : currentLine ,
129- column : currentColumn - currentToken . length + 1 ,
130- } ) ;
131- }
132- currentToken = "" ;
133- currentLine ++ ;
134- currentColumn = 1 ;
135- } else if ( char === " " || char === "\t" ) {
136- if ( currentToken . length > 0 ) {
137- this . content . push ( {
138- text : currentToken ,
139- line : currentLine ,
140- column : currentColumn - currentToken . length + 1 ,
141- } ) ;
142- }
143- currentToken = "" ;
144- currentColumn += 1 ;
145- } else {
146- currentToken += char ;
147- currentColumn += 1 ;
106+ const tokens : Token [ ] = [ ] ;
107+ for ( const [ lineNumber , line ] of text . entries ( ) ) {
108+ const lineTokens = line . split ( / \s + / ) . filter ( ( t ) => t . length > 0 ) ;
109+ let column = 0 ;
110+ for ( const token of lineTokens ) {
111+ column = line . indexOf ( token , column ) ;
112+ tokens . push ( {
113+ text : token ,
114+ line : lineNumber + 1 ,
115+ column : column + 1 ,
116+ } ) ;
148117 }
149- index ++ ;
150- }
151- if ( currentToken . length > 0 ) {
152- this . content . push ( {
153- text : currentToken ,
154- line : currentLine ,
155- column : currentColumn - currentToken . length + 1 ,
156- } ) ;
157118 }
158- this . content = this . content . map ( ( c ) => ( { ...c , text : c . text . trim ( ) } ) ) . filter ( ( c ) => c . text . length > 0 ) ;
119+
120+ return tokens ;
159121 }
160122
161123 /**
162124 * Checks the set content for errors
163125 * @returns An array of errors. An empty array means that the content is valid
164126 */
165- public verify ( line : string ) : ValidationError [ ] {
166- this . setContent ( line ) ;
167- return this . verifyNode ( this . roots , 0 , false ) ;
127+ public verify ( lines : string [ ] ) : ValidationError [ ] {
128+ const tokens = this . tokenize ( lines ) ;
129+ return this . verifyNode ( this . roots , tokens , 0 , false , true ) ;
168130 }
169131
170- private verifyNode ( nodes : AutoCompleteNode [ ] , index : number , comesFromFinal : boolean ) : ValidationError [ ] {
171- if ( comesFromFinal && this . content [ index ] . column == 0 ) {
172- const checkStart = this . verifyNode ( this . roots , index , true ) ;
173- if ( checkStart . length > 0 ) {
174- return checkStart ;
175- }
176- }
177- if ( index >= this . content . length ) {
132+ private verifyNode (
133+ nodes : AutoCompleteNode [ ] ,
134+ tokens : Token [ ] ,
135+ index : number ,
136+ comesFromFinal : boolean ,
137+ skipStartCheck = false ,
138+ ) : ValidationError [ ] {
139+ if ( index >= tokens . length ) {
178140 if ( nodes . length == 0 || comesFromFinal ) {
179141 return [ ] ;
180142 } else {
181143 return [
182144 {
183145 message : "Unexpected end of line" ,
184- line : this . content [ index - 1 ] . line ,
185- startColumn : this . content [ index - 1 ] . column + this . content [ index - 1 ] . text . length - 1 ,
186- endColumn : this . content [ index - 1 ] . column + this . content [ index - 1 ] . text . length ,
146+ line : tokens [ index - 1 ] . line ,
147+ startColumn : tokens [ index - 1 ] . column + tokens [ index - 1 ] . text . length - 1 ,
148+ endColumn : tokens [ index - 1 ] . column + tokens [ index - 1 ] . text . length ,
187149 } ,
188150 ] ;
189151 }
190152 }
153+ if ( ! skipStartCheck && tokens [ index ] . column == 1 ) {
154+ const matchesAnyRoot = this . roots . some ( ( r ) => r . word . verifyWord ( tokens [ index ] . text ) . length === 0 ) ;
155+ if ( matchesAnyRoot ) {
156+ return this . verifyNode ( this . roots , tokens , index , false , true ) ;
157+ }
158+ }
191159
192160 const foundErrors : ValidationError [ ] = [ ] ;
193161 let childErrors : ValidationError [ ] = [ ] ;
194162 for ( const n of nodes ) {
195- const v = n . word . verifyWord ( this . content [ index ] . text ) ;
163+ const v = n . word . verifyWord ( tokens [ index ] . text ) ;
196164 if ( v . length > 0 ) {
197165 foundErrors . push ( {
198166 message : v [ 0 ] ,
199- startColumn : this . content [ index ] . column ,
200- endColumn : this . content [ index ] . column + this . content [ index ] . text . length ,
201- line : this . content [ index ] . line ,
167+ startColumn : tokens [ index ] . column ,
168+ endColumn : tokens [ index ] . column + tokens [ index ] . text . length ,
169+ line : tokens [ index ] . line ,
202170 } ) ;
203171 continue ;
204172 }
205173
206- const childResult = this . verifyNode ( n . children , index + 1 , n . canBeFinal || false ) ;
174+ const childResult = this . verifyNode ( n . children , tokens , index + 1 , n . canBeFinal || false ) ;
207175 if ( childResult . length == 0 ) {
208176 return [ ] ;
209177 } else {
210178 childErrors = childErrors . concat ( childResult ) ;
211179 }
212180 }
213181 if ( childErrors . length > 0 ) {
214- return childErrors ;
182+ return deduplicateErrors ( childErrors ) ;
215183 }
216- return foundErrors ;
184+ return deduplicateErrors ( foundErrors ) ;
217185 }
218186
219187 /**
220188 * Calculates the completion options for the current content
221189 */
222- public getCompletion ( line : string ) : monaco . languages . CompletionItem [ ] {
223- this . setContent ( line ) ;
190+ public getCompletion ( lines : string [ ] ) : monaco . languages . CompletionItem [ ] {
191+ const tokens = this . tokenize ( lines ) ;
192+ const endsWithWhitespace =
193+ ( lines . length > 0 && lines [ lines . length - 1 ] . charAt ( lines [ lines . length - 1 ] . length - 1 ) . match ( / \s / ) ) ||
194+ lines [ lines . length - 1 ] . length == 0 ;
195+ if ( endsWithWhitespace ) {
196+ tokens . push ( {
197+ text : "" ,
198+ line : lines . length ,
199+ column : lines [ lines . length - 1 ] . length + 1 ,
200+ } ) ;
201+ }
224202 let result : WordCompletion [ ] = [ ] ;
225- if ( this . content . length == 0 ) {
203+ if ( tokens . length == 0 ) {
226204 for ( const r of this . roots ) {
227205 result = result . concat ( r . word . completionOptions ( "" ) ) ;
228206 }
229207 } else {
230- result = this . completeNode ( this . roots , 0 ) ;
208+ result = this . completeNode ( this . roots , tokens , 0 ) ;
231209 }
232- return this . transformResults ( result ) ;
210+ return this . transformResults ( result , tokens ) ;
233211 }
234212
235- private completeNode ( nodes : AutoCompleteNode [ ] , index : number ) : WordCompletion [ ] {
213+ private completeNode (
214+ nodes : AutoCompleteNode [ ] ,
215+ tokens : Token [ ] ,
216+ index : number ,
217+ skipStartCheck = false ,
218+ ) : WordCompletion [ ] {
219+ // check for new start
220+
221+ if ( ! skipStartCheck && tokens [ index ] . column == 1 ) {
222+ const matchesAnyRoot = this . roots . some ( ( n ) => n . word . verifyWord ( tokens [ index ] . text ) . length === 0 ) ;
223+ if ( matchesAnyRoot ) {
224+ return this . completeNode ( this . roots , tokens , index , true ) ;
225+ }
226+ }
227+
236228 let result : WordCompletion [ ] = [ ] ;
237- if ( index == this . content . length - 1 ) {
229+ if ( index == tokens . length - 1 ) {
238230 for ( const node of nodes ) {
239- result = result . concat ( node . word . completionOptions ( this . content [ index ] . text ) ) ;
231+ result = result . concat ( node . word . completionOptions ( tokens [ index ] . text ) ) ;
240232 }
241233 return result ;
242234 }
243235 for ( const n of nodes ) {
244- if ( ! n . word . verifyWord ( this . content [ index ] . text ) ) {
236+ if ( ! n . word . verifyWord ( tokens [ index ] . text ) ) {
245237 continue ;
246238 }
247- result = result . concat ( this . completeNode ( n . children , index + 1 ) ) ;
239+ result = result . concat ( this . completeNode ( n . children , tokens , index + 1 ) ) ;
248240 }
249241 return result ;
250242 }
251243
252- private transformResults ( comp : WordCompletion [ ] ) : monaco . languages . CompletionItem [ ] {
244+ private transformResults ( comp : WordCompletion [ ] , tokens : Token [ ] ) : monaco . languages . CompletionItem [ ] {
253245 const result : monaco . languages . CompletionItem [ ] = [ ] ;
254246 const filtered = comp . filter (
255247 ( c , idx ) => comp . findIndex ( ( c2 ) => c2 . insertText === c . insertText && c2 . kind === c . kind ) === idx ,
256248 ) ;
257249 for ( const c of filtered ) {
258- const r = this . transformResult ( c ) ;
250+ const r = this . transformResult ( c , tokens ) ;
259251 result . push ( r ) ;
260252 }
261253 return result ;
262254 }
263255
264- private transformResult ( comp : WordCompletion ) : monaco . languages . CompletionItem {
265- const wordStart = this . content . length == 0 ? 1 : this . content [ this . content . length - 1 ] . column - 1 ;
266- const lineNumber = this . content . length == 0 ? 1 : this . content [ this . content . length - 1 ] . line ;
256+ private transformResult ( comp : WordCompletion , tokens : Token [ ] ) : monaco . languages . CompletionItem {
257+ const wordStart = tokens . length == 0 ? 1 : tokens [ tokens . length - 1 ] . column ;
258+ const lineNumber = tokens . length == 0 ? 1 : tokens [ tokens . length - 1 ] . line ;
267259 return {
268260 insertText : comp . insertText ,
269261 kind : comp . kind ,
@@ -279,6 +271,18 @@ export class AutoCompleteTree {
279271 }
280272}
281273
274+ function deduplicateErrors ( errors : ValidationError [ ] ) : ValidationError [ ] {
275+ const seen = new Set < string > ( ) ;
276+ return errors . filter ( ( error ) => {
277+ const key = `${ error . line } -${ error . startColumn } -${ error . endColumn } -${ error . message } ` ;
278+ if ( seen . has ( key ) ) {
279+ return false ;
280+ }
281+ seen . add ( key ) ;
282+ return true ;
283+ } ) ;
284+ }
285+
282286export interface AutoCompleteNode {
283287 word : AbstractWord ;
284288 children : AutoCompleteNode [ ] ;
0 commit comments