@@ -3,16 +3,17 @@ import {
3
3
SignatureInformation ,
4
4
} from 'vscode-languageserver-types' ;
5
5
6
- import { ParserRuleContext , ParseTree } from 'antlr4' ;
7
- import {
6
+ import { ParseTreeWalker } from 'antlr4' ;
7
+ import CypherParser , {
8
8
CallClauseContext ,
9
9
ExpressionContext ,
10
10
FunctionInvocationContext ,
11
- StatementsContext ,
12
11
} from './generated-parser/CypherParser' ;
13
12
13
+ import { Token } from 'antlr4-c3' ;
14
14
import { DbSchema } from './dbSchema' ;
15
- import { findParent } from './helpers' ;
15
+ import CypherParserListener from './generated-parser/CypherParserListener' ;
16
+ import { isDefined } from './helpers' ;
16
17
import { parserWrapper } from './parserWrapper' ;
17
18
18
19
export const emptyResult : SignatureHelp = {
@@ -21,163 +22,161 @@ export const emptyResult: SignatureHelp = {
21
22
activeParameter : undefined ,
22
23
} ;
23
24
25
+ export enum MethodType {
26
+ function = 'function' ,
27
+ procedure = 'procedure' ,
28
+ }
24
29
interface ParsedMethod {
25
30
methodName : string ;
26
- numProcedureArgs : number ;
27
- }
28
-
29
- function tryParseProcedure (
30
- currentNode : ParserRuleContext ,
31
- ) : ParsedMethod | undefined {
32
- const callClause = findParent (
33
- currentNode ,
34
- ( node ) => node instanceof CallClauseContext ,
35
- ) ;
36
-
37
- if ( callClause ) {
38
- const ctx = callClause as CallClauseContext ;
39
-
40
- const methodName = ctx . procedureName ( ) . getText ( ) ;
41
- const numProcedureArgs = ctx . procedureArgument_list ( ) . length ;
42
- return {
43
- methodName : methodName ,
44
- numProcedureArgs : numProcedureArgs ,
45
- } ;
46
- } else {
47
- return undefined ;
48
- }
31
+ activeParameter : number ;
32
+ methodType : MethodType ;
49
33
}
50
34
51
- /*
52
- RETURN apoc.do.when( gets parsed as:
53
-
54
- statements
55
- / | \
56
- statement ( EOF
57
- / \
58
- RETURN expression
59
-
60
-
61
-
62
- rather than:
63
-
64
-
65
- statements
66
- / \
67
- statement EOF
68
- / \
69
- RETURN functionInvocation
70
- / \
71
- functionName (
72
-
35
+ function toSignatureHelp (
36
+ methodSignatures : Record < string , SignatureInformation > ,
37
+ parsedMethod : ParsedMethod ,
38
+ ) {
39
+ const methodName = parsedMethod . methodName ;
40
+ const method = methodSignatures [ methodName ] ;
41
+ const signatures = method ? [ method ] : [ ] ;
73
42
74
- so we need to treat that case differently because we cannot modify the
75
- relative priority of functionInvocation vs expression
43
+ const signatureHelp : SignatureHelp = {
44
+ signatures : signatures ,
45
+ activeSignature : method ? 0 : undefined ,
46
+ activeParameter : parsedMethod . activeParameter ,
47
+ } ;
48
+ return signatureHelp ;
49
+ }
76
50
77
- RETURN apoc.do.when(x gets parsed correctly (when we've started the first argument),
78
- because the parser has enough information to recognize that case
51
+ class SignatureHelper extends CypherParserListener {
52
+ result : ParsedMethod ;
53
+ constructor ( private tokens : Token [ ] , private caretToken : Token ) {
54
+ super ( ) ;
55
+ }
79
56
80
- */
81
- function findRightmostPreviousExpression (
82
- currentNode : ParserRuleContext ,
83
- ) : ParserRuleContext | undefined {
84
- const parentChildren = currentNode . parentCtx . children ;
85
- let result : ParserRuleContext | undefined = undefined ;
57
+ exitExpression = ( ctx : ExpressionContext ) => {
58
+ // If the caret is at (
59
+ if ( this . caretToken . type === CypherParser . LPAREN ) {
60
+ /* We need to compute the next token that is not
61
+ a space following the expression
62
+
63
+ Example: in the case 'RETURN apoc.do.when (' the
64
+ expression finishes before the ( and we would have a
65
+ collection of spaces between apoc.do.when and the left parenthesis
66
+ */
67
+ let index = ctx . stop . tokenIndex + 1 ;
68
+ let nextToken = this . tokens [ index ] ;
69
+
70
+ while (
71
+ nextToken . type === CypherParser . SPACE &&
72
+ index < this . tokens . length
73
+ ) {
74
+ index ++ ;
75
+ nextToken = this . tokens [ index ] ;
76
+ }
77
+
78
+ if (
79
+ this . caretToken . start === nextToken ?. start &&
80
+ this . caretToken . stop === nextToken ?. stop
81
+ ) {
82
+ const methodName = ctx . getText ( ) ;
83
+ const numMethodArgs = 0 ;
84
+ this . result = {
85
+ methodName : methodName ,
86
+ activeParameter : numMethodArgs ,
87
+ methodType : MethodType . function ,
88
+ } ;
89
+ }
90
+ }
91
+ } ;
86
92
87
- if ( parentChildren && parentChildren . length > 2 ) {
88
- let current : ParseTree | undefined =
89
- parentChildren [ parentChildren . length - 3 ] ;
90
- let expressionFound = false ;
93
+ exitFunctionInvocation = ( ctx : FunctionInvocationContext ) => {
94
+ if (
95
+ ctx . start . start <= this . caretToken . start &&
96
+ this . caretToken . stop <= ctx . stop . stop &&
97
+ // We need to check we have opened the left parenthesis
98
+ // and we won't offer the signature help on just the name
99
+ isDefined ( ctx . LPAREN ( ) )
100
+ ) {
101
+ const methodName = ctx . functionName ( ) . getText ( ) ;
102
+ const previousArguments = ctx . COMMA_list ( ) . filter ( ( arg ) => {
103
+ return arg . symbol . stop <= this . caretToken . start ;
104
+ } ) ;
105
+
106
+ this . result = {
107
+ methodName : methodName ,
108
+ activeParameter : previousArguments . length ,
109
+ methodType : MethodType . function ,
110
+ } ;
111
+ }
112
+ } ;
91
113
92
- while (
93
- current instanceof ParserRuleContext &&
94
- current . children &&
95
- current . children . length > 0 &&
96
- ! expressionFound
114
+ exitCallClause = ( ctx : CallClauseContext ) => {
115
+ if (
116
+ ctx . start . start <= this . caretToken . start &&
117
+ this . caretToken . stop <= ctx . stop . stop &&
118
+ // We need to check we have opened the left parenthesis
119
+ // and we won't offer the signature help on just the name
120
+ isDefined ( ctx . LPAREN ( ) )
97
121
) {
98
- const children = current . children ;
99
- current = children [ children . length - 1 ] ;
100
- expressionFound = current instanceof ExpressionContext ;
122
+ const methodName = ctx . procedureName ( ) . getText ( ) ;
123
+ const previousArguments = ctx . COMMA_list ( ) . filter ( ( arg ) => {
124
+ return arg . symbol . stop <= this . caretToken . start ;
125
+ } ) ;
126
+
127
+ this . result = {
128
+ methodName : methodName ,
129
+ activeParameter : previousArguments . length ,
130
+ methodType : MethodType . procedure ,
131
+ } ;
101
132
}
133
+ } ;
134
+ }
102
135
103
- result = expressionFound ? ( current as ParserRuleContext ) : undefined ;
104
- }
136
+ function findCaretToken ( tokens : Token [ ] , caretPosition : number ) : Token {
137
+ let i = 0 ;
138
+ let result : Token ;
139
+ let keepLooking = true ;
105
140
106
- return result ;
107
- }
141
+ while ( i < tokens . length && keepLooking ) {
142
+ const currentToken = tokens [ i ] ;
143
+ keepLooking = currentToken . start < caretPosition ;
108
144
109
- function tryParseFunction (
110
- currentNode : ParserRuleContext ,
111
- ) : ParsedMethod | undefined {
112
- let result : ParsedMethod | undefined = undefined ;
113
- const functionInvocation = findParent (
114
- currentNode ,
115
- ( node ) => node instanceof FunctionInvocationContext ,
116
- ) ;
117
-
118
- if ( functionInvocation ) {
119
- const ctx = functionInvocation as FunctionInvocationContext ;
120
- const methodName = ctx . functionName ( ) . getText ( ) ;
121
- const numMethodArgs = ctx . functionArgument_list ( ) . length ;
122
-
123
- result = {
124
- methodName : methodName ,
125
- numProcedureArgs : numMethodArgs ,
126
- } ;
127
- } else if (
128
- currentNode . getText ( ) === '(' &&
129
- currentNode . parentCtx instanceof StatementsContext
130
- ) {
131
- // If we finish in an expression followed by (,
132
- // take the expression text as method name
133
- const prevExpresion = findRightmostPreviousExpression ( currentNode ) ;
134
-
135
- if ( prevExpresion ) {
136
- result = {
137
- methodName : prevExpresion . getText ( ) ,
138
- numProcedureArgs : 0 ,
139
- } ;
145
+ if ( currentToken . channel === 0 && keepLooking ) {
146
+ result = currentToken ;
140
147
}
148
+
149
+ i ++ ;
141
150
}
142
151
143
152
return result ;
144
153
}
145
154
146
- function toSignatureHelp (
147
- methodSignatures : Record < string , SignatureInformation > ,
148
- parsedMethod : ParsedMethod ,
149
- ) {
150
- const methodName = parsedMethod . methodName ;
151
- const numMethodArgs = parsedMethod . numProcedureArgs ;
152
- const method = methodSignatures [ methodName ] ;
153
- const signatures = method ? [ method ] : [ ] ;
154
- const argPosition =
155
- numMethodArgs !== undefined ? Math . max ( numMethodArgs - 1 , 0 ) : undefined ;
156
-
157
- const signatureHelp : SignatureHelp = {
158
- signatures : signatures ,
159
- activeSignature : method ? 0 : undefined ,
160
- activeParameter : argPosition ,
161
- } ;
162
- return signatureHelp ;
163
- }
164
-
165
155
export function signatureHelp (
166
- textUntilPosition : string ,
156
+ fullQuery : string ,
167
157
dbSchema : DbSchema ,
158
+ caretPosition : number ,
168
159
) : SignatureHelp {
169
- const parserResult = parserWrapper . parse ( textUntilPosition ) ;
170
- const stopNode = parserResult . stopNode ;
171
160
let result : SignatureHelp = emptyResult ;
172
161
173
- const parsedProc = tryParseProcedure ( stopNode ) ;
174
- if ( parsedProc && dbSchema . procedureSignatures ) {
175
- result = toSignatureHelp ( dbSchema . procedureSignatures , parsedProc ) ;
176
- } else {
177
- const parsedFunc = tryParseFunction ( stopNode ) ;
162
+ if ( caretPosition > 0 ) {
163
+ const parserResult = parserWrapper . parse ( fullQuery ) ;
164
+
165
+ const caretToken = findCaretToken ( parserResult . tokens , caretPosition ) ;
166
+ const signatureHelper = new SignatureHelper (
167
+ parserResult . tokens ,
168
+ caretToken ,
169
+ ) ;
170
+
171
+ ParseTreeWalker . DEFAULT . walk ( signatureHelper , parserResult . result ) ;
172
+ const method = signatureHelper . result ;
178
173
179
- if ( parsedFunc && dbSchema . functionSignatures ) {
180
- result = toSignatureHelp ( dbSchema . functionSignatures , parsedFunc ) ;
174
+ if ( method !== undefined ) {
175
+ if ( method . methodType === MethodType . function ) {
176
+ result = toSignatureHelp ( dbSchema . functionSignatures , method ) ;
177
+ } else {
178
+ result = toSignatureHelp ( dbSchema . procedureSignatures , method ) ;
179
+ }
181
180
}
182
181
}
183
182
0 commit comments