7
7
*
8
8
* SPDX-License-Identifier: MIT
9
9
*/
10
- import { EditorState , StateEffect , StateField } from "@codemirror/state" ;
10
+ import { Facet , StateEffect , StateField } from "@codemirror/state" ;
11
11
import {
12
12
Command ,
13
13
EditorView ,
@@ -23,7 +23,6 @@ import { IntlShape } from "react-intl";
23
23
import {
24
24
MarkupContent ,
25
25
SignatureHelp ,
26
- SignatureHelpParams ,
27
26
SignatureHelpRequest ,
28
27
} from "vscode-languageserver-protocol" ;
29
28
import { ApiReferenceMap } from "../../../documentation/mapping/content" ;
@@ -38,6 +37,10 @@ import {
38
37
import { nameFromSignature , removeFullyQualifiedName } from "./names" ;
39
38
import { offsetToPosition } from "./positions" ;
40
39
40
+ export const automaticFacet = Facet . define < boolean , boolean > ( {
41
+ combine : ( values ) => values [ values . length - 1 ] ?? true ,
42
+ } ) ;
43
+
41
44
export const setSignatureHelpRequestPosition = StateEffect . define < number > ( { } ) ;
42
45
43
46
export const setSignatureHelpResult = StateEffect . define < SignatureHelp | null > (
@@ -49,6 +52,7 @@ class SignatureHelpState {
49
52
* -1 for no signature help requested.
50
53
*/
51
54
pos : number ;
55
+
52
56
/**
53
57
* The latest result we want to display.
54
58
*
@@ -77,44 +81,12 @@ const signatureHelpToolTipBaseTheme = EditorView.baseTheme({
77
81
} ,
78
82
} ) ;
79
83
80
- const triggerSignatureHelpRequest = async (
81
- view : EditorView ,
82
- state : EditorState
83
- ) : Promise < void > => {
84
- const uri = state . facet ( uriFacet ) ! ;
85
- const client = state . facet ( clientFacet ) ! ;
86
- const pos = state . selection . main . from ;
87
- const params : SignatureHelpParams = {
88
- textDocument : { uri } ,
89
- position : offsetToPosition ( state . doc , pos ) ,
90
- } ;
91
- try {
92
- // Must happen before other event handling that might dispatch more
93
- // changes that invalidate our position.
94
- queueMicrotask ( ( ) => {
95
- view . dispatch ( {
96
- effects : [ setSignatureHelpRequestPosition . of ( pos ) ] ,
97
- } ) ;
98
- } ) ;
99
- const result = await client . connection . sendRequest (
100
- SignatureHelpRequest . type ,
101
- params
102
- ) ;
103
- view . dispatch ( {
104
- effects : [ setSignatureHelpResult . of ( result ) ] ,
105
- } ) ;
106
- } catch ( e ) {
107
- if ( ! isErrorDueToDispose ( e ) ) {
108
- logException ( state , e , "signature-help" ) ;
109
- }
110
- view . dispatch ( {
111
- effects : [ setSignatureHelpResult . of ( null ) ] ,
112
- } ) ;
113
- }
114
- } ;
115
-
116
84
const openSignatureHelp : Command = ( view : EditorView ) => {
117
- triggerSignatureHelpRequest ( view , view . state ) ;
85
+ view . dispatch ( {
86
+ effects : [
87
+ setSignatureHelpRequestPosition . of ( view . state . selection . main . from ) ,
88
+ ] ,
89
+ } ) ;
118
90
return true ;
119
91
} ;
120
92
@@ -138,12 +110,35 @@ export const signatureHelp = (
138
110
}
139
111
}
140
112
}
113
+
141
114
// Even if we just got a result, if the position has been cleared we don't want it.
142
115
if ( pos === - 1 ) {
143
116
result = null ;
144
117
}
145
118
119
+ // By default map the previous position forward
146
120
pos = pos === - 1 ? - 1 : tr . changes . mapPos ( pos ) ;
121
+
122
+ // Did the selection moved while open? We'll re-request but keep the old result for now.
123
+ if ( pos !== - 1 && tr . selection ) {
124
+ pos = tr . selection . main . from ;
125
+ }
126
+
127
+ // Automatic triggering cases
128
+ const automatic = tr . state . facet ( automaticFacet ) . valueOf ( ) ;
129
+ if (
130
+ automatic &&
131
+ ( ( tr . docChanged && tr . isUserEvent ( "input" ) ) ||
132
+ tr . isUserEvent ( "dnd.drop.call" ) )
133
+ ) {
134
+ tr . changes . iterChanges ( ( _fromA , _toA , _fromB , _toB , inserted ) => {
135
+ if ( inserted . sliceString ( 0 ) . trim ( ) . endsWith ( "()" ) ) {
136
+ // Triggered
137
+ pos = tr . newSelection . main . from ;
138
+ }
139
+ } ) ;
140
+ }
141
+
147
142
if ( state . pos === pos && state . result === result ) {
148
143
// Avoid pointless tooltip updates. If nothing else it makes e2e tests hard.
149
144
return state ;
@@ -191,30 +186,54 @@ export const signatureHelp = (
191
186
extends BaseLanguageServerView
192
187
implements PluginValue
193
188
{
194
- constructor ( view : EditorView , private automatic : boolean ) {
189
+ private destroyed = false ;
190
+ private lastPos = - 1 ;
191
+
192
+ constructor ( view : EditorView ) {
195
193
super ( view ) ;
196
194
}
197
195
update ( update : ViewUpdate ) {
198
- if (
199
- ( update . docChanged || update . selectionSet ) &&
200
- this . view . state . field ( signatureHelpTooltipField ) . pos !== - 1
201
- ) {
202
- triggerSignatureHelpRequest ( this . view , update . state ) ;
203
- } else if ( this . automatic && update . docChanged ) {
204
- const last = update . transactions [ update . transactions . length - 1 ] ;
205
-
206
- // This needs to trigger for autocomplete adding function parens
207
- // as well as normal user input with `closebrackets` inserting
208
- // the closing bracket.
209
- if ( last . isUserEvent ( "input" ) || last . isUserEvent ( "dnd.drop.call" ) ) {
210
- last . changes . iterChanges ( ( _fromA , _toA , _fromB , _toB , inserted ) => {
211
- if ( inserted . sliceString ( 0 ) . trim ( ) . endsWith ( "()" ) ) {
212
- triggerSignatureHelpRequest ( this . view , update . state ) ;
196
+ const { view, state } = update ;
197
+ const uri = state . facet ( uriFacet ) ! ;
198
+ const client = state . facet ( clientFacet ) ! ;
199
+ const { pos } = update . state . field ( signatureHelpTooltipField ) ;
200
+ if ( this . lastPos !== pos ) {
201
+ this . lastPos = pos ;
202
+ if ( this . lastPos !== - 1 ) {
203
+ ( async ( ) => {
204
+ try {
205
+ const result = await client . connection . sendRequest (
206
+ SignatureHelpRequest . type ,
207
+ {
208
+ textDocument : { uri } ,
209
+ position : offsetToPosition ( state . doc , this . lastPos ) ,
210
+ }
211
+ ) ;
212
+ if ( ! this . destroyed ) {
213
+ view . dispatch ( {
214
+ effects : [ setSignatureHelpResult . of ( result ) ] ,
215
+ } ) ;
216
+ }
217
+ } catch ( e ) {
218
+ if ( ! isErrorDueToDispose ( e ) ) {
219
+ logException ( state , e , "signature-help" ) ;
220
+ }
221
+ // The sendRequest call can fail synchronously when disposed so we need to ensure our clean-up doesn't happen inside the CM update call.
222
+ queueMicrotask ( ( ) => {
223
+ if ( ! this . destroyed ) {
224
+ view . dispatch ( {
225
+ effects : [ setSignatureHelpResult . of ( null ) ] ,
226
+ } ) ;
227
+ }
228
+ } ) ;
213
229
}
214
- } ) ;
230
+ } ) ( ) ;
215
231
}
216
232
}
217
233
}
234
+ destroy ( ) : void {
235
+ this . destroyed = true ;
236
+ }
218
237
}
219
238
220
239
const formatSignatureHelp = (
@@ -306,10 +325,11 @@ export const signatureHelp = (
306
325
307
326
return [
308
327
// View only handles automatic triggering.
309
- ViewPlugin . define ( ( view ) => new SignatureHelpView ( view , automatic ) ) ,
328
+ ViewPlugin . define ( ( view ) => new SignatureHelpView ( view ) ) ,
310
329
signatureHelpTooltipField ,
311
330
signatureHelpToolTipBaseTheme ,
312
331
keymap . of ( signatureHelpKeymap ) ,
332
+ automaticFacet . of ( automatic ) ,
313
333
EditorView . domEventHandlers ( {
314
334
blur ( event , view ) {
315
335
// Close signature help as it interacts badly with drag and drop if
0 commit comments