1
1
import 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' ;
3
3
import { documents , getWordRangeAtPosition , parser , prettyKeywords } from '.' ;
4
- import Cache from '../../../../language/models/cache' ;
4
+ import Cache , { RpgleType , RpgleVariableType } from '../../../../language/models/cache' ;
5
5
import Declaration from '../../../../language/models/declaration' ;
6
6
import * as ileExports from './apis' ;
7
7
import skipRules from './linter/skipRules' ;
8
8
import * as Project from "./project" ;
9
9
import { getInterfaces } from './project/exportInterfaces' ;
10
10
import Parser from '../../../../language/parser' ;
11
11
import { Token } from '../../../../language/types' ;
12
+ import { getBuiltIn , getBuiltIns , getBuiltInsForType } from './apis/bif' ;
12
13
13
14
const completionKind = {
14
15
function : CompletionItemKind . Interface ,
@@ -17,6 +18,19 @@ const completionKind = {
17
18
18
19
const eol = `\n` ;
19
20
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
+
20
34
export default async function completionItemProvider ( handler : CompletionParams ) : Promise < CompletionItem [ ] > {
21
35
const items : CompletionItem [ ] = [ ] ;
22
36
const lineNumber = handler . position . line ;
@@ -30,38 +44,35 @@ export default async function completionItemProvider(handler: CompletionParams):
30
44
if ( doc ) {
31
45
const isFree = ( document . getText ( Range . create ( 0 , 0 , 0 , 6 ) ) . toUpperCase ( ) === `**FREE` ) ;
32
46
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
-
41
47
const currentLine = document . getText ( Range . create (
42
48
handler . position . line ,
43
49
0 ,
44
50
handler . position . line ,
45
51
200
46
52
) ) ;
47
53
48
- // This means we're just looking for subfields in the struct
49
54
if ( trigger === `.` ) {
55
+
56
+ //================================================
57
+ // Logic for showing subfields (subitems) on symbols
58
+ //================================================
59
+
50
60
const cursorIndex = handler . position . character ;
51
61
let tokens = Parser . lineTokens ( isFree ? currentLine : currentLine . length >= 7 ? `` . padEnd ( 7 ) + currentLine . substring ( 7 ) : `` , 0 , 0 , true ) ;
52
62
53
63
if ( tokens . length > 0 ) {
54
64
55
65
// We need to find the innermost block we are part of
56
- tokens = Parser . fromBlocksGetTokens ( tokens , cursorIndex ) ;
66
+ tokens = Parser . fromBlocksGetTokens ( tokens , cursorIndex ) . block ;
57
67
58
68
// Remove any tokens after the cursor
59
69
tokens = tokens . filter ( token => token . range . end <= cursorIndex ) ;
60
70
61
71
// 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 ;
63
74
64
- let currentDef : Declaration | undefined ;
75
+ let currentDef : Declaration | undefined ;
65
76
66
77
for ( tokenIndex ; tokenIndex < tokens . length ; tokenIndex ++ ) {
67
78
if ( tokens [ tokenIndex ] === undefined || [ `block` , `dot` , `newline` ] . includes ( tokens [ tokenIndex ] . type ) ) {
@@ -73,39 +84,91 @@ export default async function completionItemProvider(handler: CompletionParams):
73
84
if ( ! word ) break ;
74
85
75
86
if ( currentDef ) {
76
- if ( currentDef . subItems && currentDef . subItems . length > 0 ) {
87
+ if ( currentDef . type !== `procedure` && currentDef . subItems && currentDef . subItems . length > 0 ) {
77
88
currentDef = currentDef . subItems . find ( subItem => subItem . name . toUpperCase ( ) === word ) ;
78
89
}
79
90
80
91
} 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
+
93
99
// All good!
94
100
} else {
95
101
currentDef = undefined ;
96
102
}
97
103
}
98
104
}
99
105
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
+ }
109
172
}
110
173
}
111
174
} else {
@@ -128,6 +191,11 @@ export default async function completionItemProvider(handler: CompletionParams):
128
191
items . push ( ...skipRules ) ;
129
192
130
193
} else {
194
+
195
+ //================================================
196
+ // Content assist on existing symbols
197
+ //================================================
198
+
131
199
const expandScope = ( localCache : Cache ) => {
132
200
for ( const subItem of localCache . parameters ) {
133
201
const item = CompletionItem . create ( subItem . name ) ;
@@ -149,6 +217,10 @@ export default async function completionItemProvider(handler: CompletionParams):
149
217
item . insertText = `${ procedure . name } (${ procedure . subItems . map ( ( parm , index ) => `\${${ index + 1 } :${ parm . name } }` ) . join ( `:` ) } )` ;
150
218
item . detail = prettyKeywords ( procedure . keyword ) ;
151
219
item . documentation = procedure . description ;
220
+
221
+ if ( procedure . subItems . length > 0 ) {
222
+ item . command = { command : `editor.action.triggerParameterHints` , title : `Trigger Parameter Hints` } ;
223
+ }
152
224
items . push ( item ) ;
153
225
}
154
226
@@ -241,6 +313,14 @@ export default async function completionItemProvider(handler: CompletionParams):
241
313
242
314
expandScope ( doc ) ;
243
315
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
+
244
324
if ( currentProcedure ) {
245
325
// If we have the entire scope, perfect
246
326
if ( currentProcedure . scope ) {
@@ -261,6 +341,10 @@ export default async function completionItemProvider(handler: CompletionParams):
261
341
}
262
342
}
263
343
344
+ //================================================
345
+ // Auto-import logic
346
+ //================================================
347
+
264
348
if ( isFree ) {
265
349
const isInclude = currentPath . toLowerCase ( ) . endsWith ( `.rpgleinc` ) ;
266
350
const insertAt = doc . getDefinitionBlockEnd ( document . uri ) + 1 ;
@@ -317,6 +401,12 @@ export default async function completionItemProvider(handler: CompletionParams):
317
401
items . push ( item ) ;
318
402
} )
319
403
}
404
+
405
+ //================================================
406
+ // Showing the available built-in functions
407
+ //================================================
408
+
409
+ items . push ( ...builtInFunctionCompletionItems ) ;
320
410
}
321
411
}
322
412
}
0 commit comments