@@ -3,76 +3,41 @@ import { extension } from "../state";
33import type { Variable , ContextHistory , Ghost , Alias } from "../types/context" ;
44import { getSimpleName } from "../utils/utils" ;
55import { getVariablesInScope } from "./context" ;
6- import { LIQUIDJAVA_ANNOTATION_START , LJAnnotation } from "../utils/constants" ;
7-
8- type CompletionItemOptions = {
9- name : string ;
10- kind : vscode . CompletionItemKind ;
11- description ?: string ;
12- labelDetail ?: string ;
13- detail : string ;
14- documentationBlocks ?: string [ ] ;
15- codeBlocks ?: string [ ] ;
16- insertText ?: string ;
17- triggerParameterHints ?: boolean ;
18- }
19- type CompletionItemKind = "vars" | "ghosts" | "aliases" | "keywords" | "types" | "decls" | "packages" ;
6+ import { LIQUIDJAVA_ANNOTATION_START } from "../utils/constants" ;
207
218/**
229 * Registers a completion provider for LiquidJava annotations, providing context-aware suggestions based on the current context history
2310 */
2411export function registerAutocomplete ( context : vscode . ExtensionContext ) {
2512 context . subscriptions . push (
2613 vscode . languages . registerCompletionItemProvider ( "java" , {
27- provideCompletionItems ( document , position , _token , completionContext ) {
28- const annotation = getActiveLiquidJavaAnnotation ( document , position ) ;
29- if ( ! annotation || ! extension . contextHistory ) return null ;
30-
31- const isDotTrigger = completionContext . triggerKind === vscode . CompletionTriggerKind . TriggerCharacter && completionContext . triggerCharacter === "." ;
32- const receiver = isDotTrigger ? getReceiverBeforeDot ( document , position ) : null ;
14+ provideCompletionItems ( document , position ) {
15+ if ( ! isInsideLiquidJavaAnnotationString ( document , position ) || ! extension . contextHistory ) return null ;
3316 const file = document . uri . toString ( ) . replace ( "file://" , "" ) ;
3417 const nextChar = document . getText ( new vscode . Range ( position , position . translate ( 0 , 1 ) ) ) ;
35- const items = getContextCompletionItems ( extension . contextHistory , file , annotation , nextChar , isDotTrigger , receiver ) ;
36- const uniqueItems = new Map < string , vscode . CompletionItem > ( ) ;
37- items . forEach ( item => {
38- const label = typeof item . label === "string" ? item . label : item . label . label ;
39- if ( ! uniqueItems . has ( label ) ) uniqueItems . set ( label , item ) ;
40- } ) ;
41- return Array . from ( uniqueItems . values ( ) ) ;
18+ return getContextCompletionItems ( extension . contextHistory , file , nextChar ) ;
4219 } ,
43- } , '.' , '"' )
20+ } )
4421 ) ;
4522}
4623
47- function getContextCompletionItems ( context : ContextHistory , file : string , annotation : LJAnnotation , nextChar : string , isDotTrigger : boolean , receiver : string | null ) : vscode . CompletionItem [ ] {
48- const triggerParameterHints = nextChar !== "(" ;
49- if ( isDotTrigger ) {
50- if ( receiver === "this" || receiver === "old(this)" ) {
51- return getGhostCompletionItems ( context . ghosts [ file ] || [ ] , triggerParameterHints ) ;
52- }
53- return [ ] ;
54- }
24+ function getContextCompletionItems ( context : ContextHistory , file : string , nextChar : string ) : vscode . CompletionItem [ ] {
5525 const variablesInScope = getVariablesInScope ( file , extension . selection ) ;
5626 const inScope = variablesInScope !== null ;
57- const itemsHandlers : Record < CompletionItemKind , ( ) => vscode . CompletionItem [ ] > = {
58- vars : ( ) => getVariableCompletionItems ( variablesInScope || [ ] ) ,
59- ghosts : ( ) => getGhostCompletionItems ( context . ghosts [ file ] || [ ] , triggerParameterHints ) ,
60- aliases : ( ) => getAliasCompletionItems ( context . aliases , triggerParameterHints ) ,
61- keywords : ( ) => getKeywordsCompletionItems ( triggerParameterHints , inScope ) ,
62- types : ( ) => getTypesCompletionItems ( ) ,
63- decls : ( ) => getDeclsCompletionItems ( ) ,
64- packages : ( ) => [ ] , // TODO
65- }
66- const itemsMap : Record < LJAnnotation , CompletionItemKind [ ] > = {
67- Refinement : [ "vars" , "ghosts" , "aliases" , "keywords" ] ,
68- StateRefinement : [ "vars" , "ghosts" , "aliases" , "keywords" ] ,
69- Ghost : [ "types" ] ,
70- RefinementAlias : [ "types" ] ,
71- RefinementPredicate : [ "types" , "decls" ] ,
72- StateSet : [ ] ,
73- ExternalRefinementsFor : [ "packages" ]
74- }
75- return itemsMap [ annotation ] . map ( key => itemsHandlers [ key ] ( ) ) . flat ( ) ;
27+ const triggerParameterHints = nextChar !== "(" ;
28+ const variableItems = getVariableCompletionItems ( [ ...( variablesInScope || [ ] ) , ...context . globalVars ] ) ; // not including instance vars
29+ const ghostItems = getGhostCompletionItems ( context . ghosts , triggerParameterHints ) ;
30+ const aliasItems = getAliasCompletionItems ( context . aliases , triggerParameterHints ) ;
31+ const keywordItems = getKeywordsCompletionItems ( triggerParameterHints , inScope ) ;
32+ const allItems = [ ...variableItems , ...ghostItems , ...aliasItems , ...keywordItems ] ;
33+
34+ // remove duplicates
35+ const uniqueItems = new Map < string , vscode . CompletionItem > ( ) ;
36+ allItems . forEach ( item => {
37+ const label = typeof item . label === "string" ? item . label : item . label . label ;
38+ if ( ! uniqueItems . has ( label ) ) uniqueItems . set ( label , item ) ;
39+ } ) ;
40+ return Array . from ( uniqueItems . values ( ) ) ;
7641}
7742
7843function getVariableCompletionItems ( variables : Variable [ ] ) : vscode . CompletionItem [ ] {
@@ -140,21 +105,16 @@ function getKeywordsCompletionItems(triggerParameterHints: boolean, inScope: boo
140105 detail : "keyword" ,
141106 documentationBlocks : [ "Keyword referring to the **current instance**" ] ,
142107 } ) ;
143- const oldItem = getOldKeywordCompletionItem ( triggerParameterHints ) ;
144- const trueItem = createCompletionItem ( {
145- name : "true" ,
146- kind : vscode . CompletionItemKind . Keyword ,
147- description : "" ,
148- detail : "keyword" ,
149- } ) ;
150-
151- const falseItem = createCompletionItem ( {
152- name : "false" ,
108+ const oldItem = createCompletionItem ( {
109+ name : "old" ,
153110 kind : vscode . CompletionItemKind . Keyword ,
154111 description : "" ,
155112 detail : "keyword" ,
113+ documentationBlocks : [ "Keyword referring to the **previous state of the current instance**" ] ,
114+ insertText : triggerParameterHints ? "old($1)" : "old" ,
115+ triggerParameterHints,
156116 } ) ;
157- const items : vscode . CompletionItem [ ] = [ thisItem , oldItem , trueItem , falseItem ] ;
117+ const items : vscode . CompletionItem [ ] = [ thisItem , oldItem ] ;
158118 if ( ! inScope ) {
159119 const returnItem = createCompletionItem ( {
160120 name : "return" ,
@@ -168,36 +128,16 @@ function getKeywordsCompletionItems(triggerParameterHints: boolean, inScope: boo
168128 return items ;
169129}
170130
171- function getOldKeywordCompletionItem ( triggerParameterHints : boolean ) : vscode . CompletionItem {
172- return createCompletionItem ( {
173- name : "old" ,
174- kind : vscode . CompletionItemKind . Keyword ,
175- description : "" ,
176- detail : "keyword" ,
177- documentationBlocks : [ "Keyword referring to the **previous state of the current instance**" ] ,
178- insertText : triggerParameterHints ? "old($1)" : "old" ,
179- triggerParameterHints,
180- } ) ;
181- }
182-
183- function getTypesCompletionItems ( ) : vscode . CompletionItem [ ] {
184- const types = [ "int" , "double" , "float" , "boolean" ] ;
185- return types . map ( type => createCompletionItem ( {
186- name : type ,
187- kind : vscode . CompletionItemKind . Keyword ,
188- description : "" ,
189- detail : "keyword" ,
190- } ) ) ;
191- }
192-
193- function getDeclsCompletionItems ( ) : vscode . CompletionItem [ ] {
194- const decls = [ "ghost" , "type" ]
195- return decls . map ( decl => createCompletionItem ( {
196- name : decl ,
197- kind : vscode . CompletionItemKind . Keyword ,
198- description : "" ,
199- detail : "keyword" ,
200- } ) ) ;
131+ type CompletionItemOptions = {
132+ name : string ;
133+ kind : vscode . CompletionItemKind ;
134+ description ?: string ;
135+ labelDetail ?: string ;
136+ detail : string ;
137+ documentationBlocks ?: string [ ] ;
138+ codeBlocks ?: string [ ] ;
139+ insertText ?: string ;
140+ triggerParameterHints ?: boolean ;
201141}
202142
203143function createCompletionItem ( { name, kind, labelDetail, description, detail, documentationBlocks, codeBlocks, insertText, triggerParameterHints } : CompletionItemOptions ) : vscode . CompletionItem {
@@ -215,17 +155,15 @@ function createCompletionItem({ name, kind, labelDetail, description, detail, do
215155 return item ;
216156}
217157
218- function getActiveLiquidJavaAnnotation ( document : vscode . TextDocument , position : vscode . Position ) : LJAnnotation | null {
158+ function isInsideLiquidJavaAnnotationString ( document : vscode . TextDocument , position : vscode . Position ) : boolean {
219159 const textUntilCursor = document . getText ( new vscode . Range ( new vscode . Position ( 0 , 0 ) , position ) ) ;
220160 LIQUIDJAVA_ANNOTATION_START . lastIndex = 0 ;
221161 let match : RegExpExecArray | null = null ;
222162 let lastAnnotationStart = - 1 ;
223- let lastAnnotationName : LJAnnotation | null = null ;
224163 while ( ( match = LIQUIDJAVA_ANNOTATION_START . exec ( textUntilCursor ) ) !== null ) {
225164 lastAnnotationStart = match . index ;
226- lastAnnotationName = match [ 2 ] as LJAnnotation || null ;
227165 }
228- if ( lastAnnotationStart === - 1 || ! lastAnnotationName ) return null ;
166+ if ( lastAnnotationStart === - 1 ) return false ;
229167
230168 const fromLastAnnotation = textUntilCursor . slice ( lastAnnotationStart ) ;
231169 let parenthesisDepth = 0 ;
@@ -241,14 +179,5 @@ function getActiveLiquidJavaAnnotation(document: vscode.TextDocument, position:
241179 if ( char === "(" ) parenthesisDepth ++ ;
242180 if ( char === ")" ) parenthesisDepth -- ;
243181 }
244- return parenthesisDepth > 0 ? lastAnnotationName : null ;
245- }
246-
247- function getReceiverBeforeDot ( document : vscode . TextDocument , position : vscode . Position ) : string | null {
248- const prefix = document . lineAt ( position . line ) . text . slice ( 0 , position . character ) ;
249- const match = prefix . match ( / ( (?: o l d \s * \( \s * t h i s \s * \) ) | (?: [ A - Z a - z _ ] \w * ) ) \. \w * $ / ) ;
250- if ( ! match ) return null ;
251- const receiver = match [ 1 ] . trim ( ) ;
252- if ( / ^ o l d \s * \( \s * t h i s \s * \) $ / . test ( receiver ) ) return "old(this)" ;
253- return receiver ;
182+ return parenthesisDepth > 0 ;
254183}
0 commit comments