77 *
88 * SPDX-License-Identifier: MIT
99 */
10- import { EditorState , StateEffect , StateField } from "@codemirror/state" ;
10+ import { Facet , StateEffect , StateField } from "@codemirror/state" ;
1111import {
1212 Command ,
1313 EditorView ,
@@ -23,7 +23,6 @@ import { IntlShape } from "react-intl";
2323import {
2424 MarkupContent ,
2525 SignatureHelp ,
26- SignatureHelpParams ,
2726 SignatureHelpRequest ,
2827} from "vscode-languageserver-protocol" ;
2928import { ApiReferenceMap } from "../../../documentation/mapping/content" ;
@@ -38,6 +37,10 @@ import {
3837import { nameFromSignature , removeFullyQualifiedName } from "./names" ;
3938import { offsetToPosition } from "./positions" ;
4039
40+ export const automaticFacet = Facet . define < boolean , boolean > ( {
41+ combine : ( values ) => values [ values . length - 1 ] ?? true ,
42+ } ) ;
43+
4144export const setSignatureHelpRequestPosition = StateEffect . define < number > ( { } ) ;
4245
4346export const setSignatureHelpResult = StateEffect . define < SignatureHelp | null > (
@@ -49,6 +52,7 @@ class SignatureHelpState {
4952 * -1 for no signature help requested.
5053 */
5154 pos : number ;
55+
5256 /**
5357 * The latest result we want to display.
5458 *
@@ -77,44 +81,12 @@ const signatureHelpToolTipBaseTheme = EditorView.baseTheme({
7781 } ,
7882} ) ;
7983
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-
11684const openSignatureHelp : Command = ( view : EditorView ) => {
117- triggerSignatureHelpRequest ( view , view . state ) ;
85+ view . dispatch ( {
86+ effects : [
87+ setSignatureHelpRequestPosition . of ( view . state . selection . main . from ) ,
88+ ] ,
89+ } ) ;
11890 return true ;
11991} ;
12092
@@ -142,11 +114,25 @@ export const signatureHelp = (
142114 if ( pos === - 1 ) {
143115 result = null ;
144116 }
145-
117+ // By default map the previous position forward
146118 pos = pos === - 1 ? - 1 : tr . changes . mapPos ( pos ) ;
147- if ( state . pos === pos && state . result === result ) {
148- // Avoid pointless tooltip updates. If nothing else it makes e2e tests hard.
149- return state ;
119+ if ( pos !== - 1 && tr . selection ) {
120+ // Selection moved while open
121+ pos = tr . selection . main . from ;
122+ }
123+
124+ const automatic = tr . state . facet ( automaticFacet ) . valueOf ( ) ;
125+ if (
126+ automatic &&
127+ ( ( tr . docChanged && tr . isUserEvent ( "input" ) ) ||
128+ tr . isUserEvent ( "dnd.drop.call" ) )
129+ ) {
130+ tr . changes . iterChanges ( ( _fromA , _toA , _fromB , _toB , inserted ) => {
131+ if ( inserted . sliceString ( 0 ) . trim ( ) . endsWith ( "()" ) ) {
132+ // Triggered
133+ pos = tr . newSelection . main . from ;
134+ }
135+ } ) ;
150136 }
151137 return new SignatureHelpState ( pos , result ) ;
152138 } ,
@@ -191,30 +177,54 @@ export const signatureHelp = (
191177 extends BaseLanguageServerView
192178 implements PluginValue
193179 {
194- constructor ( view : EditorView , private automatic : boolean ) {
180+ private destroyed = false ;
181+ private lastPos = - 1 ;
182+
183+ constructor ( view : EditorView ) {
195184 super ( view ) ;
196185 }
197186 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 ) ;
187+ const { view, state } = update ;
188+ const uri = state . facet ( uriFacet ) ! ;
189+ const client = state . facet ( clientFacet ) ! ;
190+ const { pos } = update . state . field ( signatureHelpTooltipField ) ;
191+ if ( this . lastPos !== pos ) {
192+ this . lastPos = pos ;
193+ if ( this . lastPos !== - 1 ) {
194+ ( async ( ) => {
195+ try {
196+ const result = await client . connection . sendRequest (
197+ SignatureHelpRequest . type ,
198+ {
199+ textDocument : { uri } ,
200+ position : offsetToPosition ( state . doc , this . lastPos ) ,
201+ }
202+ ) ;
203+ if ( ! this . destroyed ) {
204+ view . dispatch ( {
205+ effects : [ setSignatureHelpResult . of ( result ) ] ,
206+ } ) ;
207+ }
208+ } catch ( e ) {
209+ if ( ! isErrorDueToDispose ( e ) ) {
210+ logException ( state , e , "signature-help" ) ;
211+ }
212+ // The sendRequest call can fail synchronously when disposed so we need to ensure our clean-up doesn't happen inside the CM update call.
213+ queueMicrotask ( ( ) => {
214+ if ( ! this . destroyed ) {
215+ view . dispatch ( {
216+ effects : [ setSignatureHelpResult . of ( null ) ] ,
217+ } ) ;
218+ }
219+ } ) ;
213220 }
214- } ) ;
221+ } ) ( ) ;
215222 }
216223 }
217224 }
225+ destroy ( ) : void {
226+ this . destroyed = true ;
227+ }
218228 }
219229
220230 const formatSignatureHelp = (
@@ -306,10 +316,11 @@ export const signatureHelp = (
306316
307317 return [
308318 // View only handles automatic triggering.
309- ViewPlugin . define ( ( view ) => new SignatureHelpView ( view , automatic ) ) ,
319+ ViewPlugin . define ( ( view ) => new SignatureHelpView ( view ) ) ,
310320 signatureHelpTooltipField ,
311321 signatureHelpToolTipBaseTheme ,
312322 keymap . of ( signatureHelpKeymap ) ,
323+ automaticFacet . of ( automatic ) ,
313324 EditorView . domEventHandlers ( {
314325 blur ( event , view ) {
315326 // Close signature help as it interacts badly with drag and drop if
0 commit comments