11import path = require( 'path' ) ;
2- import { CompletionItem , CompletionItemKind , CompletionParams , InsertTextFormat , Position , Range } from 'vscode-languageserver' ;
2+ import { CompletionItem , CompletionItemKind , CompletionParams , InsertTextFormat , InsertTextMode , Position , Range , TextEdit } from 'vscode-languageserver' ;
33import { documents , getWordRangeAtPosition , parser , prettyKeywords } from '.' ;
4- import Cache from '../../../../language/models/cache' ;
4+ import Cache , { RpgleType , RpgleVariableType } from '../../../../language/models/cache' ;
55import Declaration from '../../../../language/models/declaration' ;
66import * as ileExports from './apis' ;
77import skipRules from './linter/skipRules' ;
88import * as Project from "./project" ;
99import { getInterfaces } from './project/exportInterfaces' ;
1010import Parser from '../../../../language/parser' ;
1111import { Token } from '../../../../language/types' ;
12+ import { getBuiltIn , getBuiltIns , getBuiltInsForType } from './apis/bif' ;
1213
1314const completionKind = {
1415 function : CompletionItemKind . Interface ,
@@ -17,6 +18,19 @@ const completionKind = {
1718
1819const eol = `\n` ;
1920
21+ const builtInFunctionCompletionItems : CompletionItem [ ] = getBuiltIns ( ) . map ( builtIn => {
22+ const item = CompletionItem . create ( builtIn . name ) ;
23+ item . filterText = builtIn . name . substring ( 1 ) ;
24+ item . kind = CompletionItemKind . Function ;
25+ item . detail = builtIn . returnType || `void` ;
26+ item . documentation = `Built-in function` ;
27+ item . insertText = builtIn . name + `(\${1})` ;
28+ item . insertTextFormat = InsertTextFormat . Snippet ;
29+ item . command = { command : `editor.action.triggerParameterHints` , title : `Trigger Parameter Hints` } ;
30+ item . sortText = item . filterText ;
31+ return item ;
32+ } ) ;
33+
2034export default async function completionItemProvider ( handler : CompletionParams ) : Promise < CompletionItem [ ] > {
2135 const items : CompletionItem [ ] = [ ] ;
2236 const lineNumber = handler . position . line ;
@@ -30,38 +44,35 @@ export default async function completionItemProvider(handler: CompletionParams):
3044 if ( doc ) {
3145 const isFree = ( document . getText ( Range . create ( 0 , 0 , 0 , 6 ) ) . toUpperCase ( ) === `**FREE` ) ;
3246
33- // If they're typing inside of a procedure, let's get the stuff from there too
34- const currentProcedure = doc . procedures . find ( ( proc , index ) =>
35- proc . range . start && proc . range . end &&
36- lineNumber >= proc . range . start &&
37- ( lineNumber <= proc . range . end + 1 || index === doc . procedures . length - 1 ) &&
38- currentPath === proc . position . path
39- ) ;
40-
4147 const currentLine = document . getText ( Range . create (
4248 handler . position . line ,
4349 0 ,
4450 handler . position . line ,
4551 200
4652 ) ) ;
4753
48- // This means we're just looking for subfields in the struct
4954 if ( trigger === `.` ) {
55+
56+ //================================================
57+ // Logic for showing subfields (subitems) on symbols
58+ //================================================
59+
5060 const cursorIndex = handler . position . character ;
5161 let tokens = Parser . lineTokens ( isFree ? currentLine : currentLine . length >= 7 ? `` . padEnd ( 7 ) + currentLine . substring ( 7 ) : `` , 0 , 0 , true ) ;
5262
5363 if ( tokens . length > 0 ) {
5464
5565 // We need to find the innermost block we are part of
56- tokens = Parser . fromBlocksGetTokens ( tokens , cursorIndex ) ;
66+ tokens = Parser . fromBlocksGetTokens ( tokens , cursorIndex ) . block ;
5767
5868 // Remove any tokens after the cursor
5969 tokens = tokens . filter ( token => token . range . end <= cursorIndex ) ;
6070
6171 // Get the possible variable we're referring to
62- let tokenIndex = Parser . getReference ( tokens , cursorIndex ) ;
72+ const referenceStart = Parser . getReference ( tokens , cursorIndex ) ;
73+ let tokenIndex = referenceStart ;
6374
64- let currentDef : Declaration | undefined ;
75+ let currentDef : Declaration | undefined ;
6576
6677 for ( tokenIndex ; tokenIndex < tokens . length ; tokenIndex ++ ) {
6778 if ( tokens [ tokenIndex ] === undefined || [ `block` , `dot` , `newline` ] . includes ( tokens [ tokenIndex ] . type ) ) {
@@ -73,39 +84,91 @@ export default async function completionItemProvider(handler: CompletionParams):
7384 if ( ! word ) break ;
7485
7586 if ( currentDef ) {
76- if ( currentDef . subItems && currentDef . subItems . length > 0 ) {
87+ if ( currentDef . type !== `procedure` && currentDef . subItems && currentDef . subItems . length > 0 ) {
7788 currentDef = currentDef . subItems . find ( subItem => subItem . name . toUpperCase ( ) === word ) ;
7889 }
7990
8091 } else {
81- currentDef = [
82- // First we search the local procedure
83- currentProcedure && currentProcedure . scope ? currentProcedure . scope . parameters . find ( parm => parm . name . toUpperCase ( ) === word && parm . subItems . length > 0 ) : undefined ,
84- currentProcedure && currentProcedure . scope ? currentProcedure . scope . structs . find ( struct => struct . name . toUpperCase ( ) === word && struct . keyword [ `QUALIFIED` ] ) : undefined ,
85- currentProcedure && currentProcedure . scope ? currentProcedure . scope . constants . find ( struct => struct . subItems . length > 0 && struct . name . toUpperCase ( ) === word ) : undefined ,
86-
87- // Then we search the globals
88- doc . structs . find ( struct => struct . name . toUpperCase ( ) === word && struct . keyword [ `QUALIFIED` ] ) ,
89- doc . constants . find ( constants => constants . subItems . length > 0 && constants . name . toUpperCase ( ) === word )
90- ] . find ( x => x ) ; // find the first non-undefined item
91-
92- if ( currentDef && currentDef . subItems . length > 0 ) {
92+ currentDef = doc . findDefinition ( lineNumber , word ) ;
93+
94+ if ( currentDef ) {
95+ if ( currentDef . type === `struct` && currentDef . keyword [ `QUALIFIED` ] === undefined ) {
96+ currentDef = undefined ;
97+ }
98+
9399 // All good!
94100 } else {
95101 currentDef = undefined ;
96102 }
97103 }
98104 }
99105
100- if ( currentDef && currentDef . subItems . length > 0 ) {
101- items . push ( ...currentDef . subItems . map ( subItem => {
102- const item = CompletionItem . create ( subItem . name ) ;
103- item . kind = CompletionItemKind . Property ;
104- item . insertText = subItem . name ;
105- item . detail = prettyKeywords ( subItem . keyword ) ;
106- item . documentation = subItem . description + ` (${ currentDef . name } )` ;
107- return item ;
108- } ) ) ;
106+ let onType : RpgleType | undefined ;
107+ let onArray = false ;
108+
109+ if ( currentDef ) {
110+ if ( currentDef . subItems . length > 0 ) {
111+ items . push ( ...currentDef . subItems . map ( subItem => {
112+ const item = CompletionItem . create ( subItem . name ) ;
113+ item . kind = CompletionItemKind . Property ;
114+ item . insertText = subItem . name ;
115+ item . detail = prettyKeywords ( subItem . keyword ) ;
116+ item . documentation = subItem . description + ` (${ currentDef . name } )` ;
117+ return item ;
118+ } ) ) ;
119+ }
120+
121+ const typeDetail = doc . resolveType ( currentDef ) ;
122+ if ( typeDetail . type ) {
123+ onType = typeDetail . type . name ;
124+ onArray = typeDetail . type . isArray ;
125+ }
126+ } else if ( tokens [ referenceStart ] . type === `builtin` && tokens [ referenceStart ] . value ) {
127+ const builtIn = getBuiltIn ( tokens [ referenceStart ] . value ) ;
128+ if ( builtIn ) {
129+ onType = builtIn . returnType ;
130+ }
131+ }
132+
133+ //================================================
134+ // How about showing applicable built-in functions for a given type?
135+ //================================================
136+
137+ if ( onType ) {
138+ const usableFunctions = getBuiltInsForType ( onType , onArray ) ;
139+ if ( usableFunctions . length > 0 ) {
140+ const changeRange = Range . create (
141+ handler . position . line ,
142+ tokens [ referenceStart ] . range . start ,
143+ handler . position . line ,
144+ tokens [ tokens . length - 1 ] . range . end
145+ ) ;
146+
147+ const refValue = currentLine . substring ( tokens [ referenceStart ] . range . start , tokens [ tokens . length - 1 ] . range . start ) ;
148+
149+ for ( let func of usableFunctions ) {
150+ let builtInFunction = CompletionItem . create ( func . name . substring ( 1 ) ) ;
151+ builtInFunction . kind = CompletionItemKind . Function ;
152+
153+ const requiredParms = func . parameters . filter ( p => ! p . optional ) ;
154+
155+ builtInFunction . additionalTextEdits = [ TextEdit . del ( changeRange ) ] ;
156+ builtInFunction . insertText = `${ func . name } (` + requiredParms . map ( ( p , i ) => {
157+ if ( p . base ) {
158+ return refValue
159+ } else {
160+ return `\${${ i + 1 } :${ p . name } }`
161+ }
162+ } ) . join ( `:` ) + `)` ;
163+ builtInFunction . insertTextFormat = InsertTextFormat . Snippet ;
164+
165+ // To trigger the signature information
166+ builtInFunction . command = { command : `editor.action.triggerParameterHints` , title : `Trigger Parameter Hints` } ;
167+
168+ builtInFunction . detail = `Built-in function` ;
169+ items . push ( builtInFunction ) ;
170+ }
171+ }
109172 }
110173 }
111174 } else {
@@ -128,6 +191,11 @@ export default async function completionItemProvider(handler: CompletionParams):
128191 items . push ( ...skipRules ) ;
129192
130193 } else {
194+
195+ //================================================
196+ // Content assist on existing symbols
197+ //================================================
198+
131199 const expandScope = ( localCache : Cache ) => {
132200 for ( const subItem of localCache . parameters ) {
133201 const item = CompletionItem . create ( subItem . name ) ;
@@ -149,6 +217,10 @@ export default async function completionItemProvider(handler: CompletionParams):
149217 item . insertText = `${ procedure . name } (${ procedure . subItems . map ( ( parm , index ) => `\${${ index + 1 } :${ parm . name } }` ) . join ( `:` ) } )` ;
150218 item . detail = prettyKeywords ( procedure . keyword ) ;
151219 item . documentation = procedure . description ;
220+
221+ if ( procedure . subItems . length > 0 ) {
222+ item . command = { command : `editor.action.triggerParameterHints` , title : `Trigger Parameter Hints` } ;
223+ }
152224 items . push ( item ) ;
153225 }
154226
@@ -241,6 +313,14 @@ export default async function completionItemProvider(handler: CompletionParams):
241313
242314 expandScope ( doc ) ;
243315
316+ // If they're typing inside of a procedure, let's get the stuff from there too
317+ const currentProcedure = doc . procedures . find ( ( proc , index ) =>
318+ proc . range . start && proc . range . end &&
319+ lineNumber >= proc . range . start &&
320+ ( lineNumber <= proc . range . end + 1 || index === doc . procedures . length - 1 ) &&
321+ currentPath === proc . position . path
322+ ) ;
323+
244324 if ( currentProcedure ) {
245325 // If we have the entire scope, perfect
246326 if ( currentProcedure . scope ) {
@@ -261,6 +341,10 @@ export default async function completionItemProvider(handler: CompletionParams):
261341 }
262342 }
263343
344+ //================================================
345+ // Auto-import logic
346+ //================================================
347+
264348 if ( isFree ) {
265349 const isInclude = currentPath . toLowerCase ( ) . endsWith ( `.rpgleinc` ) ;
266350 const insertAt = doc . getDefinitionBlockEnd ( document . uri ) + 1 ;
@@ -317,6 +401,12 @@ export default async function completionItemProvider(handler: CompletionParams):
317401 items . push ( item ) ;
318402 } )
319403 }
404+
405+ //================================================
406+ // Showing the available built-in functions
407+ //================================================
408+
409+ items . push ( ...builtInFunctionCompletionItems ) ;
320410 }
321411 }
322412 }
0 commit comments