4
4
*--------------------------------------------------------------------------------------------*/
5
5
6
6
import * as vscode from 'vscode' ;
7
- import { UriConverter } from './uriConverter' ;
8
7
9
- import { FormattingOptions , TextDocumentIdentifier } from 'vscode-languageclient/node' ;
8
+ import { FormattingOptions , LanguageClient , TextDocumentIdentifier } from 'vscode-languageclient/node' ;
10
9
import * as RoslynProtocol from './roslynProtocol' ;
11
10
import { RoslynLanguageServer } from './roslynLanguageServer' ;
12
11
13
- export function registerOnAutoInsert ( languageServer : RoslynLanguageServer ) {
12
+ export function registerOnAutoInsert ( languageServer : RoslynLanguageServer , languageClient : LanguageClient ) {
14
13
let source = new vscode . CancellationTokenSource ( ) ;
15
- vscode . workspace . onDidChangeTextDocument ( async ( e ) => {
14
+
15
+ // We explicitly register against the server's didChange notification (instead of the VSCode workspace API)
16
+ // as we want to ensure that the server has processed the change before we request edits from auto insert.
17
+ // The VSCode workspace API will sometimes call this before the LSP client can send the change, leading to auto insert not working.
18
+ languageClient . getFeature ( 'textDocument/didChange' ) . onNotificationSent ( async ( event ) => {
19
+ const e = event . params ;
16
20
if ( e . contentChanges . length > 1 || e . contentChanges . length === 0 ) {
17
21
return ;
18
22
}
19
23
20
24
const change = e . contentChanges [ 0 ] ;
25
+ // TextDocumentContentChangeEvent is a union type that does not return a range if the change event is for a full document.
26
+ // Full document changes are not supported for onautoinsert.
27
+ if ( ! ( 'range' in change ) ) {
28
+ return ;
29
+ }
30
+
31
+ // Convert to a VSCode range for ease of handling.
32
+ const vscodeRange = languageClient . protocol2CodeConverter . asRange ( change . range ) ;
21
33
22
- if ( ! change . range . isEmpty ) {
34
+ // Empty or multiline changes are not supported for onautoinsert.
35
+ if ( ! vscodeRange . isEmpty || ! vscodeRange . isSingleLine ) {
23
36
return ;
24
37
}
25
38
39
+ // We need to convert to a vscode TextDocument to apply the correct capabilities.
40
+ const uri = languageClient . protocol2CodeConverter . asUri ( e . textDocument . uri ) ;
41
+ // This is a no-op because the document is already open (in order to be edited).
42
+ const document = await vscode . workspace . openTextDocument ( uri ) ;
43
+
26
44
const onAutoInsertFeature = languageServer . getOnAutoInsertFeature ( ) ;
27
- const onAutoInsertOptions = onAutoInsertFeature ?. getOptions ( e . document ) ;
45
+ const onAutoInsertOptions = onAutoInsertFeature ?. getOptions ( document ) ;
28
46
const vsTriggerCharacters = onAutoInsertOptions ?. _vs_triggerCharacters ;
29
47
30
48
if ( vsTriggerCharacters === undefined ) {
@@ -38,10 +56,17 @@ export function registerOnAutoInsert(languageServer: RoslynLanguageServer) {
38
56
return ;
39
57
}
40
58
59
+ // We have a single line range so we can compute the length by comparing the start and end character positions.
60
+ const rangeLength = vscodeRange . end . character - vscodeRange . start . character ;
61
+
62
+ // The server expects the request position to represent the caret position in the text after the change has already been applied.
63
+ // We need to calculate what that position would be after the change is applied and send that to the server.
64
+ const position = vscodeRange . start . translate ( 0 , change . text . length - rangeLength ) ;
65
+
41
66
source . cancel ( ) ;
42
67
source = new vscode . CancellationTokenSource ( ) ;
43
68
try {
44
- await applyAutoInsertEdit ( e , changeTrimmed , languageServer , source . token ) ;
69
+ await applyAutoInsertEdit ( position , changeTrimmed , e . textDocument , uri , languageServer , source . token ) ;
45
70
} catch ( e ) {
46
71
if ( e instanceof vscode . CancellationError ) {
47
72
return ;
@@ -53,25 +78,18 @@ export function registerOnAutoInsert(languageServer: RoslynLanguageServer) {
53
78
}
54
79
55
80
async function applyAutoInsertEdit (
56
- e : vscode . TextDocumentChangeEvent ,
57
- changeTrimmed : string ,
81
+ position : vscode . Position ,
82
+ changeTextTrimmed : string ,
83
+ textDocumentIdentifier : TextDocumentIdentifier ,
84
+ uri : vscode . Uri ,
58
85
languageServer : RoslynLanguageServer ,
59
86
token : vscode . CancellationToken
60
87
) {
61
- const change = e . contentChanges [ 0 ] ;
62
- // The server expects the request position to represent the caret position in the text after the change has already been applied.
63
- // We need to calculate what that position would be after the change is applied and send that to the server.
64
- const position = new vscode . Position (
65
- change . range . start . line ,
66
- change . range . start . character + ( change . text . length - change . rangeLength )
67
- ) ;
68
- const uri = UriConverter . serialize ( e . document . uri ) ;
69
- const textDocument = TextDocumentIdentifier . create ( uri ) ;
70
88
const formattingOptions = getFormattingOptions ( ) ;
71
89
const request : RoslynProtocol . OnAutoInsertParams = {
72
- _vs_textDocument : textDocument ,
90
+ _vs_textDocument : textDocumentIdentifier ,
73
91
_vs_position : position ,
74
- _vs_ch : changeTrimmed ,
92
+ _vs_ch : changeTextTrimmed ,
75
93
_vs_options : formattingOptions ,
76
94
} ;
77
95
@@ -84,7 +102,7 @@ async function applyAutoInsertEdit(
84
102
const code : any = vscode ;
85
103
const textEdits = [ new code . SnippetTextEdit ( new vscode . Range ( startPosition , endPosition ) , docComment ) ] ;
86
104
const edit = new vscode . WorkspaceEdit ( ) ;
87
- edit . set ( e . document . uri , textEdits ) ;
105
+ edit . set ( uri , textEdits ) ;
88
106
89
107
const applied = vscode . workspace . applyEdit ( edit ) ;
90
108
if ( ! applied ) {
0 commit comments