@@ -8,7 +8,7 @@ import * as positron from 'positron';
8
8
import * as path from 'path' ;
9
9
10
10
import { ExtensionContext } from 'vscode' ;
11
- import { Command , Executable , ExecuteCommandRequest , InlineCompletionItem , InlineCompletionRequest , LanguageClient , LanguageClientOptions , NotificationType , RequestType , ServerOptions , TransportKind } from 'vscode-languageclient/node' ;
11
+ import { Command , DidChangeTextDocumentNotification , DidChangeTextDocumentParams , DidCloseTextDocumentNotification , DidOpenTextDocumentNotification , Executable , ExecuteCommandRequest , InlineCompletionItem , InlineCompletionRequest , LanguageClient , LanguageClientOptions , Middleware , NotebookDocumentMiddleware , NotificationType , RequestType , ServerOptions , TextDocumentItem , TransportKind } from 'vscode-languageclient/node' ;
12
12
import { arch , platform } from 'os' ;
13
13
import { ALL_DOCUMENTS_SELECTOR } from './constants.js' ;
14
14
@@ -98,7 +98,7 @@ export class CopilotService implements vscode.Disposable {
98
98
/** The CopilotService singleton instance. */
99
99
private static _instance ?: CopilotService ;
100
100
101
- private _client ?: CopilotLanguageClient ;
101
+ private _clientManager ?: CopilotLanguageClientManager ;
102
102
103
103
/** The cancellation token for the current operation. */
104
104
private _cancellationToken : vscode . CancellationTokenSource | null = null ;
@@ -125,9 +125,9 @@ export class CopilotService implements vscode.Disposable {
125
125
) { }
126
126
127
127
/** Get the Copilot language client. */
128
- private client ( ) : CopilotLanguageClient {
129
- if ( ! this . _client ) {
130
- // The client does not exist, create it.
128
+ private client ( ) : LanguageClient {
129
+ if ( ! this . _clientManager ) {
130
+ // The client manager does not exist, create it.
131
131
const serverName = platform ( ) === 'win32' ? 'copilot-language-server.exe' : 'copilot-language-server' ;
132
132
let serverPath = path . join ( this . _context . extensionPath , 'resources' , 'copilot' ) ;
133
133
@@ -148,9 +148,9 @@ export class CopilotService implements vscode.Disposable {
148
148
name : packageJSON . name ,
149
149
version : packageJSON . version ,
150
150
} ;
151
- this . _client = new CopilotLanguageClient ( executable , editorPluginInfo ) ;
151
+ this . _clientManager = new CopilotLanguageClientManager ( executable , editorPluginInfo ) ;
152
152
}
153
- return this . _client ;
153
+ return this . _clientManager . client ;
154
154
}
155
155
156
156
/**
@@ -234,8 +234,14 @@ export class CopilotService implements vscode.Disposable {
234
234
) : Promise < vscode . InlineCompletionItem [ ] | vscode . InlineCompletionList | undefined > {
235
235
const client = this . client ( ) ;
236
236
const params = client . code2ProtocolConverter . asInlineCompletionParams ( textDocument , position , context ) ;
237
- const result = await client . sendRequest ( InlineCompletionRequest . type , params , token ) ;
238
- return client . protocol2CodeConverter . asInlineCompletionResult ( result ) ;
237
+ client . debug ( `Sending inline completion request: ${ JSON . stringify ( params ) } ` ) ;
238
+ try {
239
+ const result = await client . sendRequest ( InlineCompletionRequest . type , params , token ) ;
240
+ return client . protocol2CodeConverter . asInlineCompletionResult ( result ) ;
241
+ } catch ( error ) {
242
+ client . debug ( `Error getting inline completions: ${ error } ` ) ;
243
+ throw error ;
244
+ }
239
245
}
240
246
241
247
private asCopilotInlineCompletionItem ( completionItem : vscode . InlineCompletionItem , updatedInsertText ?: string ) : InlineCompletionItem {
@@ -277,19 +283,11 @@ export class CopilotService implements vscode.Disposable {
277
283
}
278
284
}
279
285
280
- export class CopilotLanguageClient implements vscode . Disposable {
286
+ export class CopilotLanguageClientManager implements vscode . Disposable {
281
287
private readonly _disposables : vscode . Disposable [ ] = [ ] ;
282
288
283
289
/** The wrapped language client. */
284
- private readonly _client : LanguageClient ;
285
-
286
- // Expose wrapped properties from the language client.
287
- public code2ProtocolConverter : typeof this . _client . code2ProtocolConverter ;
288
- public onNotification : typeof this . _client . onNotification ;
289
- public protocol2CodeConverter : typeof this . _client . protocol2CodeConverter ;
290
- public sendNotification : typeof this . _client . sendNotification ;
291
- public sendRequest : typeof this . _client . sendRequest ;
292
- public start : typeof this . _client . start ;
290
+ public readonly client : LanguageClient ;
293
291
294
292
/**
295
293
* @param executable The language server executable.
@@ -318,31 +316,84 @@ export class CopilotLanguageClient implements vscode.Disposable {
318
316
} ,
319
317
editorPluginInfo,
320
318
} ,
319
+ middleware : {
320
+ notebooks : this . createNotebookMiddleware ( ) ,
321
+ } ,
321
322
} ;
322
323
323
324
// Create the client.
324
- this . _client = new LanguageClient (
325
+ this . client = new LanguageClient (
325
326
'githubCopilotLanguageServer' ,
326
327
'GitHub Copilot Language Server' ,
327
328
serverOptions ,
328
329
clientOptions ,
329
330
) ;
330
- this . _disposables . push ( this . _client ) ;
331
+ this . _disposables . push ( this . client ) ;
331
332
332
333
// Log status changes for debugging.
333
334
this . _disposables . push (
334
- this . _client . onNotification ( DidChangeStatusNotification . type , ( params : DidChangeStatusParams ) => {
335
- outputChannel . debug ( `DidChangeStatusNotification: ${ JSON . stringify ( params ) } ` ) ;
335
+ this . client . onNotification ( DidChangeStatusNotification . type , ( params : DidChangeStatusParams ) => {
336
+ this . client . debug ( `DidChangeStatusNotification: ${ JSON . stringify ( params ) } ` ) ;
336
337
} )
337
338
) ;
339
+ }
338
340
339
- // Expose wrapped properties from the language client.
340
- this . code2ProtocolConverter = this . _client . code2ProtocolConverter ;
341
- this . onNotification = this . _client . onNotification . bind ( this . _client ) ;
342
- this . protocol2CodeConverter = this . _client . protocol2CodeConverter ;
343
- this . sendNotification = this . _client . sendNotification . bind ( this . _client ) ;
344
- this . sendRequest = this . _client . sendRequest . bind ( this . _client ) ;
345
- this . start = this . _client . start . bind ( this . _client ) ;
341
+ private createNotebookMiddleware ( ) : NotebookDocumentMiddleware [ 'notebooks' ] {
342
+ // The Copilot language server advertises that it supports notebooks
343
+ // (in the initialize result) which causes vscode-languageclient to
344
+ // send notebookDocument/did* notifications instead of textDocument/did*
345
+ // notifications. Servers are expected to create the text documents
346
+ // referenced in the notebook document, however, the Copilot server
347
+ // doesn't seem to do that, causing "document not found" errors.
348
+ // See: https://github.com/posit-dev/positron/issues/8061.
349
+ //
350
+ // This middleware intercepts notebookDocument/did* notifications and sends
351
+ // textDocument/did* notifications for each affected cell.
352
+ //
353
+ // TODO: The current implementation treats each cell independently,
354
+ // so the server will be aware of all cells in a notebook, but not
355
+ // their structure, kind, outputs, etc.
356
+
357
+ const manager = this ;
358
+ return {
359
+ async didOpen ( notebookDocument , cells , next ) {
360
+ for ( const cell of cells ) {
361
+ const params = manager . client . code2ProtocolConverter . asOpenTextDocumentParams ( cell . document ) ;
362
+ await manager . client . sendNotification ( DidOpenTextDocumentNotification . type , params ) ;
363
+ }
364
+ return next ( notebookDocument , cells ) ;
365
+ } ,
366
+ async didChange ( event , next ) {
367
+ for ( const cell of event . cells ?. structure ?. didOpen ?? [ ] ) {
368
+ const params = manager . client . code2ProtocolConverter . asOpenTextDocumentParams ( cell . document ) ;
369
+ await manager . client . sendNotification ( DidOpenTextDocumentNotification . type , params ) ;
370
+ }
371
+
372
+ for ( const cell of event . cells ?. structure ?. didClose ?? [ ] ) {
373
+ const params = manager . client . code2ProtocolConverter . asCloseTextDocumentParams ( cell . document ) ;
374
+ await manager . client . sendNotification ( DidCloseTextDocumentNotification . type , params ) ;
375
+ }
376
+
377
+ for ( const change of event . cells ?. textContent ?? [ ] ) {
378
+ const params : DidChangeTextDocumentParams = {
379
+ textDocument : manager . client . code2ProtocolConverter . asVersionedTextDocumentIdentifier ( change . document ) ,
380
+ contentChanges : change . contentChanges . map ( change => ( {
381
+ range : manager . client . code2ProtocolConverter . asRange ( change . range ) ,
382
+ text : change . text ,
383
+ } ) ) ,
384
+ } ;
385
+ await manager . client . sendNotification ( DidChangeTextDocumentNotification . type , params ) ;
386
+ }
387
+ return await next ( event ) ;
388
+ } ,
389
+ async didClose ( notebookDocument , cells , next ) {
390
+ for ( const cell of cells ) {
391
+ const params = manager . client . code2ProtocolConverter . asCloseTextDocumentParams ( cell . document ) ;
392
+ await manager . client . sendNotification ( DidCloseTextDocumentNotification . type , params ) ;
393
+ }
394
+ return await next ( notebookDocument , cells ) ;
395
+ } ,
396
+ } ;
346
397
}
347
398
348
399
dispose ( ) : void {
0 commit comments