@@ -3,41 +3,76 @@ 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 } from "../utils/constants" ;
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" ;
720
821/**
922 * Registers a completion provider for LiquidJava annotations, providing context-aware suggestions based on the current context history
1023 */
1124export function registerAutocomplete ( context : vscode . ExtensionContext ) {
1225 context . subscriptions . push (
1326 vscode . languages . registerCompletionItemProvider ( "java" , {
14- provideCompletionItems ( document , position ) {
15- if ( ! isInsideLiquidJavaAnnotationString ( document , position ) || ! extension . contextHistory ) return null ;
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 ;
1633 const file = document . uri . toString ( ) . replace ( "file://" , "" ) ;
1734 const nextChar = document . getText ( new vscode . Range ( position , position . translate ( 0 , 1 ) ) ) ;
18- return getContextCompletionItems ( extension . contextHistory , file , nextChar ) ;
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 ( ) ) ;
1942 } ,
20- } )
43+ } , '.' , '"' )
2144 ) ;
2245}
2346
24- function getContextCompletionItems ( context : ContextHistory , file : string , nextChar : string ) : vscode . CompletionItem [ ] {
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+ }
2555 const variablesInScope = getVariablesInScope ( file , extension . selection ) ;
2656 const inScope = variablesInScope !== null ;
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 ( ) ) ;
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 ( ) ;
4176}
4277
4378function getVariableCompletionItems ( variables : Variable [ ] ) : vscode . CompletionItem [ ] {
@@ -105,16 +140,21 @@ function getKeywordsCompletionItems(triggerParameterHints: boolean, inScope: boo
105140 detail : "keyword" ,
106141 documentationBlocks : [ "Keyword referring to the **current instance**" ] ,
107142 } ) ;
108- const oldItem = createCompletionItem ( {
109- name : "old" ,
143+ const oldItem = getOldKeywordCompletionItem ( triggerParameterHints ) ;
144+ const trueItem = createCompletionItem ( {
145+ name : "true" ,
110146 kind : vscode . CompletionItemKind . Keyword ,
111147 description : "" ,
112148 detail : "keyword" ,
113- documentationBlocks : [ "Keyword referring to the **previous state of the current instance**" ] ,
114- insertText : triggerParameterHints ? "old($1)" : "old" ,
115- triggerParameterHints,
116149 } ) ;
117- const items : vscode . CompletionItem [ ] = [ thisItem , oldItem ] ;
150+
151+ const falseItem = createCompletionItem ( {
152+ name : "false" ,
153+ kind : vscode . CompletionItemKind . Keyword ,
154+ description : "" ,
155+ detail : "keyword" ,
156+ } ) ;
157+ const items : vscode . CompletionItem [ ] = [ thisItem , oldItem , trueItem , falseItem ] ;
118158 if ( ! inScope ) {
119159 const returnItem = createCompletionItem ( {
120160 name : "return" ,
@@ -128,16 +168,36 @@ function getKeywordsCompletionItems(triggerParameterHints: boolean, inScope: boo
128168 return items ;
129169}
130170
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 ;
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+ } ) ) ;
141201}
142202
143203function createCompletionItem ( { name, kind, labelDetail, description, detail, documentationBlocks, codeBlocks, insertText, triggerParameterHints } : CompletionItemOptions ) : vscode . CompletionItem {
@@ -155,15 +215,17 @@ function createCompletionItem({ name, kind, labelDetail, description, detail, do
155215 return item ;
156216}
157217
158- function isInsideLiquidJavaAnnotationString ( document : vscode . TextDocument , position : vscode . Position ) : boolean {
218+ function getActiveLiquidJavaAnnotation ( document : vscode . TextDocument , position : vscode . Position ) : LJAnnotation | null {
159219 const textUntilCursor = document . getText ( new vscode . Range ( new vscode . Position ( 0 , 0 ) , position ) ) ;
160220 LIQUIDJAVA_ANNOTATION_START . lastIndex = 0 ;
161221 let match : RegExpExecArray | null = null ;
162222 let lastAnnotationStart = - 1 ;
223+ let lastAnnotationName : LJAnnotation | null = null ;
163224 while ( ( match = LIQUIDJAVA_ANNOTATION_START . exec ( textUntilCursor ) ) !== null ) {
164225 lastAnnotationStart = match . index ;
226+ lastAnnotationName = match [ 2 ] as LJAnnotation || null ;
165227 }
166- if ( lastAnnotationStart === - 1 ) return false ;
228+ if ( lastAnnotationStart === - 1 || ! lastAnnotationName ) return null ;
167229
168230 const fromLastAnnotation = textUntilCursor . slice ( lastAnnotationStart ) ;
169231 let parenthesisDepth = 0 ;
@@ -179,5 +241,14 @@ function isInsideLiquidJavaAnnotationString(document: vscode.TextDocument, posit
179241 if ( char === "(" ) parenthesisDepth ++ ;
180242 if ( char === ")" ) parenthesisDepth -- ;
181243 }
182- return parenthesisDepth > 0 ;
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 ;
183254}
0 commit comments