2023-09-05 16:02:27 -07:00
|
|
|
import type * as LSP from 'vscode-languageserver-protocol'
|
|
|
|
import Client from './client'
|
2024-02-19 12:33:16 -08:00
|
|
|
import { SemanticToken, deserializeTokens } from './kcl/semantic_tokens'
|
|
|
|
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
|
2024-03-12 23:57:43 -07:00
|
|
|
import { CopilotLspCompletionParams } from 'wasm-lib/kcl/bindings/CopilotLspCompletionParams'
|
|
|
|
import { CopilotCompletionResponse } from 'wasm-lib/kcl/bindings/CopilotCompletionResponse'
|
|
|
|
import { CopilotAcceptCompletionParams } from 'wasm-lib/kcl/bindings/CopilotAcceptCompletionParams'
|
|
|
|
import { CopilotRejectCompletionParams } from 'wasm-lib/kcl/bindings/CopilotRejectCompletionParams'
|
2024-04-15 17:18:32 -07:00
|
|
|
import { UpdateUnitsParams } from 'wasm-lib/kcl/bindings/UpdateUnitsParams'
|
|
|
|
import { UpdateCanExecuteParams } from 'wasm-lib/kcl/bindings/UpdateCanExecuteParams'
|
|
|
|
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
|
|
|
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
2024-04-16 21:36:19 -07:00
|
|
|
import { LspWorker } from './types'
|
2024-02-15 13:56:31 -08:00
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
|
|
|
|
|
|
|
// Client to server then server to client
|
|
|
|
interface LSPRequestMap {
|
|
|
|
initialize: [LSP.InitializeParams, LSP.InitializeResult]
|
|
|
|
'textDocument/hover': [LSP.HoverParams, LSP.Hover]
|
|
|
|
'textDocument/completion': [
|
|
|
|
LSP.CompletionParams,
|
|
|
|
LSP.CompletionItem[] | LSP.CompletionList | null
|
|
|
|
]
|
|
|
|
'textDocument/semanticTokens/full': [
|
|
|
|
LSP.SemanticTokensParams,
|
|
|
|
LSP.SemanticTokens
|
|
|
|
]
|
2024-04-15 17:18:32 -07:00
|
|
|
'textDocument/formatting': [
|
|
|
|
LSP.DocumentFormattingParams,
|
|
|
|
LSP.TextEdit[] | null
|
|
|
|
]
|
|
|
|
'textDocument/foldingRange': [LSP.FoldingRangeParams, LSP.FoldingRange[]]
|
2024-04-12 21:32:57 -07:00
|
|
|
'copilot/getCompletions': [
|
|
|
|
CopilotLspCompletionParams,
|
|
|
|
CopilotCompletionResponse
|
|
|
|
]
|
2024-04-15 17:18:32 -07:00
|
|
|
'kcl/updateUnits': [UpdateUnitsParams, UpdateUnitsResponse | null]
|
|
|
|
'kcl/updateCanExecute': [UpdateCanExecuteParams, UpdateCanExecuteResponse]
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Client to server
|
|
|
|
interface LSPNotifyMap {
|
|
|
|
initialized: LSP.InitializedParams
|
|
|
|
'textDocument/didChange': LSP.DidChangeTextDocumentParams
|
|
|
|
'textDocument/didOpen': LSP.DidOpenTextDocumentParams
|
2024-03-11 17:50:31 -07:00
|
|
|
'textDocument/didClose': LSP.DidCloseTextDocumentParams
|
|
|
|
'workspace/didChangeWorkspaceFolders': LSP.DidChangeWorkspaceFoldersParams
|
2024-03-12 13:37:47 -07:00
|
|
|
'workspace/didCreateFiles': LSP.CreateFilesParams
|
|
|
|
'workspace/didRenameFiles': LSP.RenameFilesParams
|
|
|
|
'workspace/didDeleteFiles': LSP.DeleteFilesParams
|
2024-04-15 17:18:32 -07:00
|
|
|
'copilot/notifyAccepted': CopilotAcceptCompletionParams
|
|
|
|
'copilot/notifyRejected': CopilotRejectCompletionParams
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface LanguageServerClientOptions {
|
|
|
|
client: Client
|
2024-04-16 21:36:19 -07:00
|
|
|
name: LspWorker
|
2024-02-19 12:33:16 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface LanguageServerOptions {
|
|
|
|
// We assume this is the main project directory, we are currently working in.
|
|
|
|
workspaceFolders: LSP.WorkspaceFolder[]
|
|
|
|
documentUri: string
|
|
|
|
allowHTMLContent: boolean
|
|
|
|
client: LanguageServerClient
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
export class LanguageServerClient {
|
|
|
|
private client: Client
|
2024-04-15 17:18:32 -07:00
|
|
|
readonly name: string
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
public ready: boolean
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
readonly plugins: LanguageServerPlugin[]
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
public initializePromise: Promise<void>
|
|
|
|
|
|
|
|
private isUpdatingSemanticTokens: boolean = false
|
|
|
|
private semanticTokens: SemanticToken[] = []
|
2024-02-15 13:56:31 -08:00
|
|
|
private queuedUids: string[] = []
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
constructor(options: LanguageServerClientOptions) {
|
|
|
|
this.plugins = []
|
|
|
|
this.client = options.client
|
2024-02-19 12:33:16 -08:00
|
|
|
this.name = options.name
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
this.ready = false
|
|
|
|
|
2024-02-15 13:56:31 -08:00
|
|
|
this.queuedUids = []
|
2023-09-05 16:02:27 -07:00
|
|
|
this.initializePromise = this.initialize()
|
|
|
|
}
|
|
|
|
|
|
|
|
async initialize() {
|
|
|
|
// Start the client in the background.
|
2024-02-19 12:33:16 -08:00
|
|
|
this.client.setNotifyFn(this.processNotifications.bind(this))
|
2024-02-11 12:59:00 +11:00
|
|
|
this.client.start()
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
this.ready = true
|
|
|
|
}
|
|
|
|
|
2024-02-19 12:33:16 -08:00
|
|
|
getName(): string {
|
|
|
|
return this.name
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
getServerCapabilities(): LSP.ServerCapabilities<any> {
|
|
|
|
return this.client.getServerCapabilities()
|
|
|
|
}
|
|
|
|
|
|
|
|
close() {}
|
|
|
|
|
|
|
|
textDocumentDidOpen(params: LSP.DidOpenTextDocumentParams) {
|
|
|
|
this.notify('textDocument/didOpen', params)
|
|
|
|
|
2024-03-11 17:50:31 -07:00
|
|
|
// Update the facet of the plugins to the correct value.
|
|
|
|
for (const plugin of this.plugins) {
|
|
|
|
plugin.documentUri = params.textDocument.uri
|
|
|
|
plugin.languageId = params.textDocument.languageId
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
this.updateSemanticTokens(params.textDocument.uri)
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
textDocumentDidChange(params: LSP.DidChangeTextDocumentParams) {
|
|
|
|
this.notify('textDocument/didChange', params)
|
2024-02-11 12:59:00 +11:00
|
|
|
this.updateSemanticTokens(params.textDocument.uri)
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
2024-03-11 17:50:31 -07:00
|
|
|
textDocumentDidClose(params: LSP.DidCloseTextDocumentParams) {
|
|
|
|
this.notify('textDocument/didClose', params)
|
|
|
|
}
|
|
|
|
|
|
|
|
workspaceDidChangeWorkspaceFolders(
|
|
|
|
added: LSP.WorkspaceFolder[],
|
|
|
|
removed: LSP.WorkspaceFolder[]
|
|
|
|
) {
|
|
|
|
// Add all the current workspace folders in the plugin to removed.
|
|
|
|
for (const plugin of this.plugins) {
|
|
|
|
removed.push(...plugin.workspaceFolders)
|
|
|
|
}
|
|
|
|
this.notify('workspace/didChangeWorkspaceFolders', {
|
|
|
|
event: { added, removed },
|
|
|
|
})
|
|
|
|
|
|
|
|
// Add all the new workspace folders to the plugins.
|
|
|
|
for (const plugin of this.plugins) {
|
|
|
|
plugin.workspaceFolders = added
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-12 13:37:47 -07:00
|
|
|
workspaceDidCreateFiles(params: LSP.CreateFilesParams) {
|
|
|
|
this.notify('workspace/didCreateFiles', params)
|
|
|
|
}
|
|
|
|
|
|
|
|
workspaceDidRenameFiles(params: LSP.RenameFilesParams) {
|
|
|
|
this.notify('workspace/didRenameFiles', params)
|
|
|
|
}
|
|
|
|
|
|
|
|
workspaceDidDeleteFiles(params: LSP.DeleteFilesParams) {
|
|
|
|
this.notify('workspace/didDeleteFiles', params)
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
async updateSemanticTokens(uri: string) {
|
2024-02-19 12:33:16 -08:00
|
|
|
const serverCapabilities = this.getServerCapabilities()
|
|
|
|
if (!serverCapabilities.semanticTokensProvider) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
// Make sure we can only run, if we aren't already running.
|
|
|
|
if (!this.isUpdatingSemanticTokens) {
|
|
|
|
this.isUpdatingSemanticTokens = true
|
|
|
|
|
|
|
|
const result = await this.request('textDocument/semanticTokens/full', {
|
|
|
|
textDocument: {
|
|
|
|
uri,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
this.semanticTokens = deserializeTokens(
|
|
|
|
result.data,
|
|
|
|
this.getServerCapabilities().semanticTokensProvider
|
|
|
|
)
|
|
|
|
|
|
|
|
this.isUpdatingSemanticTokens = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getSemanticTokens(): SemanticToken[] {
|
|
|
|
return this.semanticTokens
|
|
|
|
}
|
|
|
|
|
|
|
|
async textDocumentHover(params: LSP.HoverParams) {
|
2024-02-19 12:33:16 -08:00
|
|
|
const serverCapabilities = this.getServerCapabilities()
|
|
|
|
if (!serverCapabilities.hoverProvider) {
|
|
|
|
return
|
|
|
|
}
|
2023-09-05 16:02:27 -07:00
|
|
|
return await this.request('textDocument/hover', params)
|
|
|
|
}
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
async textDocumentFormatting(params: LSP.DocumentFormattingParams) {
|
|
|
|
const serverCapabilities = this.getServerCapabilities()
|
|
|
|
if (!serverCapabilities.documentFormattingProvider) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return await this.request('textDocument/formatting', params)
|
|
|
|
}
|
|
|
|
|
|
|
|
async textDocumentFoldingRange(params: LSP.FoldingRangeParams) {
|
|
|
|
const serverCapabilities = this.getServerCapabilities()
|
|
|
|
if (!serverCapabilities.foldingRangeProvider) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
return await this.request('textDocument/foldingRange', params)
|
|
|
|
}
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
async textDocumentCompletion(params: LSP.CompletionParams) {
|
2024-02-19 12:33:16 -08:00
|
|
|
const serverCapabilities = this.getServerCapabilities()
|
|
|
|
if (!serverCapabilities.completionProvider) {
|
|
|
|
return
|
|
|
|
}
|
2024-04-16 21:36:19 -07:00
|
|
|
const response = await this.request('textDocument/completion', params)
|
|
|
|
return response
|
2023-09-05 16:02:27 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
attachPlugin(plugin: LanguageServerPlugin) {
|
|
|
|
this.plugins.push(plugin)
|
|
|
|
}
|
|
|
|
|
|
|
|
detachPlugin(plugin: LanguageServerPlugin) {
|
|
|
|
const i = this.plugins.indexOf(plugin)
|
|
|
|
if (i === -1) return
|
|
|
|
this.plugins.splice(i, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
private request<K extends keyof LSPRequestMap>(
|
|
|
|
method: K,
|
|
|
|
params: LSPRequestMap[K][0]
|
|
|
|
): Promise<LSPRequestMap[K][1]> {
|
|
|
|
return this.client.request(method, params) as Promise<LSPRequestMap[K][1]>
|
|
|
|
}
|
|
|
|
|
|
|
|
private notify<K extends keyof LSPNotifyMap>(
|
|
|
|
method: K,
|
|
|
|
params: LSPNotifyMap[K]
|
|
|
|
): void {
|
|
|
|
return this.client.notify(method, params)
|
|
|
|
}
|
|
|
|
|
2024-03-12 23:57:43 -07:00
|
|
|
async getCompletion(params: CopilotLspCompletionParams) {
|
2024-04-12 21:32:57 -07:00
|
|
|
const response = await this.request('copilot/getCompletions', params)
|
2024-02-15 13:56:31 -08:00
|
|
|
//
|
|
|
|
this.queuedUids = [...response.completions.map((c) => c.uuid)]
|
|
|
|
return response
|
|
|
|
}
|
|
|
|
|
|
|
|
async accept(uuid: string) {
|
|
|
|
const badUids = this.queuedUids.filter((u) => u !== uuid)
|
|
|
|
this.queuedUids = []
|
2024-04-15 17:18:32 -07:00
|
|
|
this.acceptCompletion({ uuid })
|
|
|
|
this.rejectCompletions({ uuids: badUids })
|
2024-02-15 13:56:31 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
async reject() {
|
|
|
|
const badUids = this.queuedUids
|
|
|
|
this.queuedUids = []
|
2024-04-15 17:18:32 -07:00
|
|
|
this.rejectCompletions({ uuids: badUids })
|
|
|
|
}
|
|
|
|
|
|
|
|
acceptCompletion(params: CopilotAcceptCompletionParams) {
|
|
|
|
this.notify('copilot/notifyAccepted', params)
|
|
|
|
}
|
|
|
|
|
|
|
|
rejectCompletions(params: CopilotRejectCompletionParams) {
|
|
|
|
this.notify('copilot/notifyRejected', params)
|
2024-02-15 13:56:31 -08:00
|
|
|
}
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
async updateUnits(
|
|
|
|
params: UpdateUnitsParams
|
|
|
|
): Promise<UpdateUnitsResponse | null> {
|
|
|
|
return await this.request('kcl/updateUnits', params)
|
2024-02-15 13:56:31 -08:00
|
|
|
}
|
2024-02-19 12:33:16 -08:00
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
async updateCanExecute(
|
|
|
|
params: UpdateCanExecuteParams
|
|
|
|
): Promise<UpdateCanExecuteResponse> {
|
|
|
|
return await this.request('kcl/updateCanExecute', params)
|
2024-02-15 13:56:31 -08:00
|
|
|
}
|
|
|
|
|
2024-02-19 12:33:16 -08:00
|
|
|
private processNotifications(notification: LSP.NotificationMessage) {
|
2023-09-05 16:02:27 -07:00
|
|
|
for (const plugin of this.plugins) plugin.processNotification(notification)
|
|
|
|
}
|
|
|
|
}
|