pull lsp client out into a fake module (#2846)
* initial commit Signed-off-by: Jess Frazelle <github@jessfraz.com> tsc passing Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> working Signed-off-by: Jess Frazelle <github@jessfraz.com> fixups Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanups Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * udpates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -56,3 +56,5 @@ src-tauri/gen
|
||||
|
||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
Mac_App_Distribution.provisionprofile
|
||||
|
||||
*.tsbuildinfo
|
||||
|
@ -1,5 +1,6 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
dist
|
||||
coverage
|
||||
|
||||
# Ignore Rust projects:
|
||||
@ -9,5 +10,6 @@ src/wasm-lib/pkg
|
||||
src/wasm-lib/kcl/bindings
|
||||
e2e/playwright/export-snapshots
|
||||
|
||||
|
||||
# XState generated files
|
||||
src/machines/**.typegen.ts
|
||||
|
@ -73,8 +73,8 @@
|
||||
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm": "(cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
@ -147,7 +147,7 @@
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.4.31",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.8.0",
|
||||
"prettier": "^2.8.8",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"vite": "^5.2.9",
|
||||
|
6
packages/codemirror-lsp-client/.gitignore
vendored
Normal file
6
packages/codemirror-lsp-client/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
build
|
||||
dist
|
||||
tsconfig.tsbuildinfo
|
||||
*.d.ts
|
||||
*.js
|
35
packages/codemirror-lsp-client/package.json
Normal file
35
packages/codemirror-lsp-client/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@kittycad/codemirror-lsp-client",
|
||||
"version": "1.0.0",
|
||||
"description": "An LSP client for the codemirror editor.",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"types": "dist/index.d.ts",
|
||||
"module": "dist/index.js",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/KittyCAD/modeling-app",
|
||||
"author": "Zoo Engineering Team",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.16.3",
|
||||
"@codemirror/language": "^6.10.2",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@lezer/highlight": "^1.2.0",
|
||||
"@ts-stack/markdown": "^1.5.0",
|
||||
"json-rpc-2.0": "^1.7.0",
|
||||
"typescript": "^5.5.2",
|
||||
"vscode-languageserver-protocol": "^3.17.5",
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.9",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import * as vsrpc from 'vscode-jsonrpc'
|
||||
|
||||
import { Codec } from '.'
|
||||
import Bytes from './bytes'
|
||||
import PromiseMap from './map'
|
||||
import Queue from './queue'
|
||||
import Tracer from '../tracer'
|
||||
import { Codec } from '../codec'
|
||||
import Tracer from './tracer'
|
||||
import PromiseMap from './map'
|
||||
|
||||
export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
readonly responses: PromiseMap<number | string, vsrpc.ResponseMessage> =
|
||||
@ -15,9 +15,12 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
new Queue<vsrpc.RequestMessage>()
|
||||
|
||||
readonly #start: Promise<void>
|
||||
private trace: boolean = false
|
||||
|
||||
constructor() {
|
||||
constructor(trace?: boolean) {
|
||||
super()
|
||||
this.trace = trace || false
|
||||
|
||||
this.#start = this.start()
|
||||
}
|
||||
|
||||
@ -64,7 +67,10 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
contentLength = null
|
||||
|
||||
const message = JSON.parse(delimited) as vsrpc.Message
|
||||
Tracer.server(message)
|
||||
|
||||
if (this.trace) {
|
||||
Tracer.server(message)
|
||||
}
|
||||
|
||||
// demux the message stream
|
||||
if (vsrpc.Message.isResponse(message) && null != message.id) {
|
||||
@ -85,7 +91,9 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
|
||||
add(bytes: Uint8Array): void {
|
||||
const message = Codec.decode(bytes) as vsrpc.Message
|
||||
Tracer.server(message)
|
||||
if (this.trace) {
|
||||
Tracer.server(message)
|
||||
}
|
||||
|
||||
// demux the message stream
|
||||
if (vsrpc.Message.isResponse(message) && null != message.id) {
|
@ -1,12 +1,16 @@
|
||||
import * as jsrpc from 'json-rpc-2.0'
|
||||
import * as vsrpc from 'vscode-jsonrpc'
|
||||
|
||||
import Bytes from './codec/bytes'
|
||||
import StreamDemuxer from './codec/demuxer'
|
||||
import Headers from './codec/headers'
|
||||
import Queue from './codec/queue'
|
||||
import Bytes from './bytes'
|
||||
import StreamDemuxer from './demuxer'
|
||||
import Headers from './headers'
|
||||
import Queue from './queue'
|
||||
import Tracer from './tracer'
|
||||
import { LspWorkerEventType, LspWorker } from './types'
|
||||
|
||||
export enum LspWorkerEventType {
|
||||
Init = 'init',
|
||||
Call = 'call',
|
||||
}
|
||||
|
||||
export const encoder = new TextEncoder()
|
||||
export const decoder = new TextDecoder()
|
||||
@ -33,16 +37,24 @@ export class IntoServer
|
||||
implements AsyncGenerator<Uint8Array, never, void>
|
||||
{
|
||||
private worker: Worker | null = null
|
||||
private type_: LspWorker | null = null
|
||||
constructor(type_?: LspWorker, worker?: Worker) {
|
||||
private type_: String | null = null
|
||||
|
||||
private trace: boolean = false
|
||||
|
||||
constructor(type_?: String, worker?: Worker, trace?: boolean) {
|
||||
super()
|
||||
if (worker && type_) {
|
||||
this.worker = worker
|
||||
this.type_ = type_
|
||||
}
|
||||
|
||||
this.trace = trace || false
|
||||
}
|
||||
enqueue(item: Uint8Array): void {
|
||||
Tracer.client(Headers.remove(decoder.decode(item)))
|
||||
if (this.trace) {
|
||||
Tracer.client(Headers.remove(decoder.decode(item)))
|
||||
}
|
||||
|
||||
if (this.worker) {
|
||||
this.worker.postMessage({
|
||||
worker: this.type_,
|
||||
@ -71,7 +83,7 @@ export namespace FromServer {
|
||||
// Calls private method .start() which can throw.
|
||||
// This is an odd one of the bunch but try/catch seems most suitable here.
|
||||
try {
|
||||
return new StreamDemuxer()
|
||||
return new StreamDemuxer(false)
|
||||
} catch (e: any) {
|
||||
return e
|
||||
}
|
13
packages/codemirror-lsp-client/src/client/codec/tracer.ts
Normal file
13
packages/codemirror-lsp-client/src/client/codec/tracer.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Message } from 'vscode-languageserver-protocol'
|
||||
|
||||
export default class Tracer {
|
||||
static client(message: string): void {
|
||||
console.log('lsp client message', message)
|
||||
}
|
||||
|
||||
static server(input: string | Message): void {
|
||||
const message: string =
|
||||
typeof input === 'string' ? input : JSON.stringify(input)
|
||||
console.log('lsp server message', message)
|
||||
}
|
||||
}
|
@ -1,15 +1,8 @@
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import Client from './client'
|
||||
import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin'
|
||||
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'
|
||||
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'
|
||||
import { LspWorker } from './types'
|
||||
|
||||
import { FromServer, IntoServer } from './codec'
|
||||
import Client from './jsonrpc'
|
||||
import { LanguageServerPlugin } from '../plugin/lsp'
|
||||
|
||||
// https://microsoft.github.io/language-server-protocol/specifications/specification-current/
|
||||
|
||||
@ -30,12 +23,6 @@ interface LSPRequestMap {
|
||||
LSP.TextEdit[] | null
|
||||
]
|
||||
'textDocument/foldingRange': [LSP.FoldingRangeParams, LSP.FoldingRange[]]
|
||||
'copilot/getCompletions': [
|
||||
CopilotLspCompletionParams,
|
||||
CopilotCompletionResponse
|
||||
]
|
||||
'kcl/updateUnits': [UpdateUnitsParams, UpdateUnitsResponse | null]
|
||||
'kcl/updateCanExecute': [UpdateCanExecuteParams, UpdateCanExecuteResponse]
|
||||
}
|
||||
|
||||
// Client to server
|
||||
@ -48,26 +35,18 @@ interface LSPNotifyMap {
|
||||
'workspace/didCreateFiles': LSP.CreateFilesParams
|
||||
'workspace/didRenameFiles': LSP.RenameFilesParams
|
||||
'workspace/didDeleteFiles': LSP.DeleteFilesParams
|
||||
'copilot/notifyAccepted': CopilotAcceptCompletionParams
|
||||
'copilot/notifyRejected': CopilotRejectCompletionParams
|
||||
}
|
||||
|
||||
export interface LanguageServerClientOptions {
|
||||
client: Client
|
||||
name: LspWorker
|
||||
}
|
||||
|
||||
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
|
||||
name: string
|
||||
fromServer: FromServer
|
||||
intoServer: IntoServer
|
||||
initializedCallback: () => void
|
||||
}
|
||||
|
||||
export class LanguageServerClient {
|
||||
private client: Client
|
||||
readonly name: LspWorker
|
||||
readonly name: string
|
||||
|
||||
public ready: boolean
|
||||
|
||||
@ -75,16 +54,18 @@ export class LanguageServerClient {
|
||||
|
||||
public initializePromise: Promise<void>
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
constructor(options: LanguageServerClientOptions) {
|
||||
this.plugins = []
|
||||
this.client = options.client
|
||||
this.name = options.name
|
||||
this.plugins = []
|
||||
|
||||
this.client = new Client(
|
||||
options.fromServer,
|
||||
options.intoServer,
|
||||
options.initializedCallback
|
||||
)
|
||||
|
||||
this.ready = false
|
||||
|
||||
this.queuedUids = []
|
||||
this.initializePromise = this.initialize()
|
||||
}
|
||||
|
||||
@ -198,6 +179,10 @@ export class LanguageServerClient {
|
||||
return this.client.request(method, params) as Promise<LSPRequestMap[K][1]>
|
||||
}
|
||||
|
||||
requestCustom<P, R>(method: string, params: P): Promise<R> {
|
||||
return this.client.request(method, params) as Promise<R>
|
||||
}
|
||||
|
||||
private notify<K extends keyof LSPNotifyMap>(
|
||||
method: K,
|
||||
params: LSPNotifyMap[K]
|
||||
@ -205,44 +190,8 @@ export class LanguageServerClient {
|
||||
return this.client.notify(method, params)
|
||||
}
|
||||
|
||||
async getCompletion(params: CopilotLspCompletionParams) {
|
||||
const response = await this.request('copilot/getCompletions', params)
|
||||
//
|
||||
this.queuedUids = [...response.completions.map((c) => c.uuid)]
|
||||
return response
|
||||
}
|
||||
|
||||
async accept(uuid: string) {
|
||||
const badUids = this.queuedUids.filter((u) => u !== uuid)
|
||||
this.queuedUids = []
|
||||
this.acceptCompletion({ uuid })
|
||||
this.rejectCompletions({ uuids: badUids })
|
||||
}
|
||||
|
||||
async reject() {
|
||||
const badUids = this.queuedUids
|
||||
this.queuedUids = []
|
||||
this.rejectCompletions({ uuids: badUids })
|
||||
}
|
||||
|
||||
acceptCompletion(params: CopilotAcceptCompletionParams) {
|
||||
this.notify('copilot/notifyAccepted', params)
|
||||
}
|
||||
|
||||
rejectCompletions(params: CopilotRejectCompletionParams) {
|
||||
this.notify('copilot/notifyRejected', params)
|
||||
}
|
||||
|
||||
async updateUnits(
|
||||
params: UpdateUnitsParams
|
||||
): Promise<UpdateUnitsResponse | null> {
|
||||
return await this.request('kcl/updateUnits', params)
|
||||
}
|
||||
|
||||
async updateCanExecute(
|
||||
params: UpdateCanExecuteParams
|
||||
): Promise<UpdateCanExecuteResponse> {
|
||||
return await this.request('kcl/updateCanExecute', params)
|
||||
notifyCustom<P>(method: string, params: P): void {
|
||||
return this.client.notify(method, params)
|
||||
}
|
||||
|
||||
private processNotifications(notification: LSP.NotificationMessage) {
|
@ -6,7 +6,6 @@ import {
|
||||
unregisterServerCapability,
|
||||
} from './server-capability-registration'
|
||||
import { Codec, FromServer, IntoServer } from './codec'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const client_capabilities: LSP.ClientCapabilities = {
|
||||
textDocument: {
|
||||
@ -130,7 +129,9 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
||||
this.serverCapabilities,
|
||||
capabilityRegistration
|
||||
)
|
||||
if (err(caps)) return (this.serverCapabilities = {})
|
||||
if (caps instanceof Error) {
|
||||
return (this.serverCapabilities = {})
|
||||
}
|
||||
this.serverCapabilities = caps
|
||||
}
|
||||
)
|
||||
@ -145,7 +146,9 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
||||
this.serverCapabilities,
|
||||
capabilityUnregistration
|
||||
)
|
||||
if (err(caps)) return (this.serverCapabilities = {})
|
||||
if (caps instanceof Error) {
|
||||
return (this.serverCapabilities = {})
|
||||
}
|
||||
this.serverCapabilities = caps
|
||||
}
|
||||
)
|
||||
@ -157,7 +160,7 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
||||
{
|
||||
processId: null,
|
||||
clientInfo: {
|
||||
name: 'kcl-language-client',
|
||||
name: 'codemirror-lsp-client',
|
||||
},
|
||||
capabilities: client_capabilities,
|
||||
rootUri: null,
|
113
packages/codemirror-lsp-client/src/index.ts
Normal file
113
packages/codemirror-lsp-client/src/index.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { autocompletion } from '@codemirror/autocomplete'
|
||||
import { foldService, syntaxTree } from '@codemirror/language'
|
||||
import { Extension, EditorState } from '@codemirror/state'
|
||||
import { ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||
|
||||
import {
|
||||
docPathFacet,
|
||||
LanguageServerPlugin,
|
||||
LanguageServerPluginSpec,
|
||||
languageId,
|
||||
workspaceFolders,
|
||||
LanguageServerOptions,
|
||||
} from './plugin/lsp'
|
||||
import { offsetToPos } from './plugin/util'
|
||||
|
||||
export type { LanguageServerClientOptions } from './client'
|
||||
export { LanguageServerClient } from './client'
|
||||
export {
|
||||
Codec,
|
||||
FromServer,
|
||||
IntoServer,
|
||||
LspWorkerEventType,
|
||||
} from './client/codec'
|
||||
export type { LanguageServerOptions } from './plugin/lsp'
|
||||
export type { TransactionInfo, RelevantUpdate } from './plugin/annotations'
|
||||
export { updateInfo, TransactionAnnotation } from './plugin/annotations'
|
||||
export {
|
||||
LanguageServerPlugin,
|
||||
LanguageServerPluginSpec,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
workspaceFolders,
|
||||
} from './plugin/lsp'
|
||||
export { posToOffset, offsetToPos } from './plugin/util'
|
||||
|
||||
export function lspPlugin(options: LanguageServerOptions): Extension {
|
||||
let plugin: LanguageServerPlugin | null = null
|
||||
const viewPlugin = ViewPlugin.define(
|
||||
(view) => (plugin = new LanguageServerPlugin(options, view)),
|
||||
new LanguageServerPluginSpec()
|
||||
)
|
||||
|
||||
let ext = [
|
||||
docPathFacet.of(options.documentUri),
|
||||
languageId.of('kcl'),
|
||||
workspaceFolders.of(options.workspaceFolders),
|
||||
viewPlugin,
|
||||
foldService.of((state: EditorState, lineStart: number, lineEnd: number) => {
|
||||
if (plugin == null) return null
|
||||
// Get the folding ranges from the language server.
|
||||
// Since this is async we directly need to update the folding ranges after.
|
||||
return plugin?.foldingRange(lineStart, lineEnd)
|
||||
}),
|
||||
]
|
||||
|
||||
if (options.client.getServerCapabilities().completionProvider) {
|
||||
ext.push(
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
if (plugin === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { state, pos, explicit } = context
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
131
packages/codemirror-lsp-client/src/plugin/annotations.ts
Normal file
131
packages/codemirror-lsp-client/src/plugin/annotations.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import { hasNextSnippetField, pickedCompletion } from '@codemirror/autocomplete'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import type { ViewUpdate } from '@codemirror/view'
|
||||
|
||||
export enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
FormatCode = 'format-code',
|
||||
Diagnostics = 'diagnostics',
|
||||
}
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
|
||||
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
|
||||
|
||||
export enum TransactionAnnotation {
|
||||
Remote = 'remote',
|
||||
UserSelect = 'user.select',
|
||||
UserInput = 'user.input',
|
||||
UserMove = 'user.move',
|
||||
UserDelete = 'user.delete',
|
||||
UserUndo = 'user.undo',
|
||||
UserRedo = 'user.redo',
|
||||
|
||||
SemanticTokens = 'SemanticTokens',
|
||||
FormatCode = 'FormatCode',
|
||||
Diagnostics = 'Diagnostics',
|
||||
|
||||
PickedCompletion = 'PickedCompletion',
|
||||
}
|
||||
|
||||
export interface TransactionInfo {
|
||||
annotations: TransactionAnnotation[]
|
||||
time: number | null
|
||||
docChanged: boolean
|
||||
addToHistory: boolean
|
||||
inSnippet: boolean
|
||||
transaction: Transaction
|
||||
}
|
||||
|
||||
export const updateInfo = (update: ViewUpdate): TransactionInfo[] => {
|
||||
let transactionInfos: TransactionInfo[] = []
|
||||
|
||||
for (const tr of update.transactions) {
|
||||
let annotations: TransactionAnnotation[] = []
|
||||
|
||||
if (tr.isUserEvent('select')) {
|
||||
annotations.push(TransactionAnnotation.UserSelect)
|
||||
}
|
||||
|
||||
if (tr.isUserEvent('input')) {
|
||||
annotations.push(TransactionAnnotation.UserInput)
|
||||
}
|
||||
if (tr.isUserEvent('delete')) {
|
||||
annotations.push(TransactionAnnotation.UserDelete)
|
||||
}
|
||||
if (tr.isUserEvent('undo')) {
|
||||
annotations.push(TransactionAnnotation.UserUndo)
|
||||
}
|
||||
if (tr.isUserEvent('redo')) {
|
||||
annotations.push(TransactionAnnotation.UserRedo)
|
||||
}
|
||||
if (tr.isUserEvent('move')) {
|
||||
annotations.push(TransactionAnnotation.UserMove)
|
||||
}
|
||||
|
||||
if (tr.annotation(pickedCompletion) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.PickedCompletion)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspSemanticTokensEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.SemanticTokens)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspFormatCodeEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.FormatCode)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspDiagnosticsEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Diagnostics)
|
||||
}
|
||||
|
||||
if (tr.annotation(Transaction.remote) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Remote)
|
||||
}
|
||||
|
||||
transactionInfos.push({
|
||||
annotations,
|
||||
time: tr.annotation(Transaction.time) || null,
|
||||
docChanged: tr.docChanged,
|
||||
addToHistory: tr.annotation(Transaction.addToHistory) || false,
|
||||
inSnippet: hasNextSnippetField(update.state),
|
||||
transaction: tr,
|
||||
})
|
||||
}
|
||||
|
||||
return transactionInfos
|
||||
}
|
||||
|
||||
export interface RelevantUpdate {
|
||||
overall: boolean
|
||||
userSelect: boolean
|
||||
time: number | null
|
||||
}
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
info.docChanged ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
}
|
51
packages/codemirror-lsp-client/src/plugin/autocomplete.ts
Normal file
51
packages/codemirror-lsp-client/src/plugin/autocomplete.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
acceptCompletion,
|
||||
clearSnippet,
|
||||
closeCompletion,
|
||||
hasNextSnippetField,
|
||||
moveCompletionSelection,
|
||||
nextSnippetField,
|
||||
prevSnippetField,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { Prec } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding } from '@codemirror/view'
|
||||
|
||||
import { CompletionItemKind } from 'vscode-languageserver-protocol'
|
||||
|
||||
export const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
) as Record<CompletionItemKind, string>
|
||||
|
||||
const lspAutocompleteKeymap: readonly KeyBinding[] = [
|
||||
{ key: 'Ctrl-Space', run: startCompletion },
|
||||
{
|
||||
key: 'Escape',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (clearSnippet(view)) return true
|
||||
|
||||
return closeCompletion(view)
|
||||
},
|
||||
},
|
||||
{ key: 'ArrowDown', run: moveCompletionSelection(true) },
|
||||
{ key: 'ArrowUp', run: moveCompletionSelection(false) },
|
||||
{ key: 'PageDown', run: moveCompletionSelection(true, 'page') },
|
||||
{ key: 'PageUp', run: moveCompletionSelection(false, 'page') },
|
||||
{ key: 'Enter', run: acceptCompletion },
|
||||
{
|
||||
key: 'Tab',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (hasNextSnippetField(view.state)) {
|
||||
const result = nextSnippetField(view)
|
||||
return result
|
||||
}
|
||||
|
||||
return acceptCompletion(view)
|
||||
},
|
||||
shift: prevSnippetField,
|
||||
},
|
||||
]
|
||||
|
||||
export const lspAutocompleteKeymapExt = Prec.highest(
|
||||
keymap.computeN([], () => [lspAutocompleteKeymap])
|
||||
)
|
27
packages/codemirror-lsp-client/src/plugin/format.ts
Normal file
27
packages/codemirror-lsp-client/src/plugin/format.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Extension, Prec } from '@codemirror/state'
|
||||
import { EditorView, keymap, KeyBinding, ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { LanguageServerPlugin } from './lsp'
|
||||
|
||||
export default function lspFormatExt(
|
||||
plugin: ViewPlugin<LanguageServerPlugin>
|
||||
): Extension {
|
||||
const formatKeymap: readonly KeyBinding[] = [
|
||||
{
|
||||
key: 'Alt-Shift-f',
|
||||
run: (view: EditorView) => {
|
||||
let value = view.plugin(plugin)
|
||||
if (!value) return false
|
||||
value.requestFormatting()
|
||||
return true
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
// Create an extension for the key mappings.
|
||||
const formatKeymapExt = Prec.highest(
|
||||
keymap.computeN([], () => [formatKeymap])
|
||||
)
|
||||
|
||||
return formatKeymapExt
|
||||
}
|
22
packages/codemirror-lsp-client/src/plugin/hover.ts
Normal file
22
packages/codemirror-lsp-client/src/plugin/hover.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Extension } from '@codemirror/state'
|
||||
import { hoverTooltip, tooltips, ViewPlugin } from '@codemirror/view'
|
||||
|
||||
import { LanguageServerPlugin } from './lsp'
|
||||
import { offsetToPos } from './util'
|
||||
|
||||
export default function lspHoverExt(
|
||||
plugin: ViewPlugin<LanguageServerPlugin>
|
||||
): Extension {
|
||||
return [
|
||||
hoverTooltip((view, pos) => {
|
||||
const value = view.plugin(plugin)
|
||||
return (
|
||||
value?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ??
|
||||
null
|
||||
)
|
||||
}),
|
||||
tooltips({
|
||||
position: 'absolute',
|
||||
}),
|
||||
]
|
||||
}
|
21
packages/codemirror-lsp-client/src/plugin/indent.ts
Normal file
21
packages/codemirror-lsp-client/src/plugin/indent.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { indentService } from '@codemirror/language'
|
||||
import { Extension } from '@codemirror/state'
|
||||
|
||||
export default function lspIndentExt(): Extension {
|
||||
// Match the indentation of the previous line (if present).
|
||||
return indentService.of((context, pos) => {
|
||||
try {
|
||||
const previousLine = context.lineAt(pos, -1)
|
||||
const previousLineText = previousLine.text.replaceAll(
|
||||
'\t',
|
||||
' '.repeat(context.state.tabSize)
|
||||
)
|
||||
const match = previousLineText.match(/^(\s)*/)
|
||||
if (match === null || match.length <= 0) return null
|
||||
return match[0].length
|
||||
} catch (err) {
|
||||
console.error('Error in codemirror indentService', err)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
14
packages/codemirror-lsp-client/src/plugin/lint.ts
Normal file
14
packages/codemirror-lsp-client/src/plugin/lint.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Extension } from '@codemirror/state'
|
||||
import { linter, forEachDiagnostic, Diagnostic } from '@codemirror/lint'
|
||||
|
||||
import { LanguageServerPlugin } from './lsp'
|
||||
|
||||
export default function lspLintExt(): Extension {
|
||||
return linter((view) => {
|
||||
let diagnostics: Diagnostic[] = []
|
||||
forEachDiagnostic(view.state, (d: Diagnostic, from: number, to: number) => {
|
||||
diagnostics.push(d)
|
||||
})
|
||||
return diagnostics
|
||||
})
|
||||
}
|
@ -1,56 +1,43 @@
|
||||
import {
|
||||
completeFromList,
|
||||
hasNextSnippetField,
|
||||
pickedCompletion,
|
||||
snippetCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import {
|
||||
Facet,
|
||||
StateEffect,
|
||||
StateField,
|
||||
Extension,
|
||||
Annotation,
|
||||
Transaction,
|
||||
} from '@codemirror/state'
|
||||
import {
|
||||
EditorView,
|
||||
Tooltip,
|
||||
Decoration,
|
||||
DecorationSet,
|
||||
} from '@codemirror/view'
|
||||
import { URI } from 'vscode-uri'
|
||||
import {
|
||||
DiagnosticSeverity,
|
||||
CompletionItemKind,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol'
|
||||
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import type {
|
||||
Completion,
|
||||
CompletionContext,
|
||||
CompletionResult,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { completeFromList, snippetCompletion } from '@codemirror/autocomplete'
|
||||
import { Facet, StateEffect, Extension, Transaction } from '@codemirror/state'
|
||||
import type {
|
||||
ViewUpdate,
|
||||
PluginValue,
|
||||
PluginSpec,
|
||||
ViewPlugin,
|
||||
} from '@codemirror/view'
|
||||
import { EditorView, Tooltip } from '@codemirror/view'
|
||||
import { setDiagnosticsEffect } from '@codemirror/lint'
|
||||
|
||||
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
|
||||
import type { ViewUpdate, PluginValue } from '@codemirror/view'
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import { Marked } from '@ts-stack/markdown'
|
||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||
import { Program, ProgramMemory } from 'lang/wasm'
|
||||
import { codeManager, editorManager } from 'lib/singletons'
|
||||
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
|
||||
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
|
||||
import { copilotPluginEvent } from './copilot'
|
||||
import { codeManagerUpdateEvent } from 'lang/codeManager'
|
||||
import {
|
||||
modelingMachineEvent,
|
||||
updateOutsideEditorEvent,
|
||||
setDiagnosticsEvent,
|
||||
} from 'editor/manager'
|
||||
import { SemanticToken, getTag } from 'editor/plugins/lsp/semantic_token'
|
||||
import { highlightingFor } from '@codemirror/language'
|
||||
DiagnosticSeverity,
|
||||
CompletionTriggerKind,
|
||||
} from 'vscode-languageserver-protocol'
|
||||
import { URI } from 'vscode-uri'
|
||||
|
||||
import { LanguageServerClient } from '../client'
|
||||
import {
|
||||
lspSemanticTokensEvent,
|
||||
lspFormatCodeEvent,
|
||||
lspDiagnosticsEvent,
|
||||
relevantUpdate,
|
||||
} from './annotations'
|
||||
import { CompletionItemKindMap } from './autocomplete'
|
||||
import { addToken, SemanticToken } from './semantic-tokens'
|
||||
import { deferExecution, posToOffset, formatMarkdownContents } from './util'
|
||||
import { lspAutocompleteKeymapExt } from './autocomplete'
|
||||
import lspHoverExt from './hover'
|
||||
import lspFormatExt from './format'
|
||||
import lspIndentExt from './indent'
|
||||
import lspLintExt from './lint'
|
||||
import lspSemanticTokensExt from './semantic-tokens'
|
||||
|
||||
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
|
||||
export const docPathFacet = Facet.define<string, string>({
|
||||
@ -62,202 +49,18 @@ export const workspaceFolders = Facet.define<
|
||||
LSP.WorkspaceFolder[]
|
||||
>({ combine: useLast })
|
||||
|
||||
enum LspAnnotation {
|
||||
SemanticTokens = 'semantic-tokens',
|
||||
}
|
||||
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
|
||||
processLspNotification?: (
|
||||
plugin: LanguageServerPlugin,
|
||||
notification: LSP.NotificationMessage
|
||||
) => void
|
||||
|
||||
const lspEvent = Annotation.define<LspAnnotation>()
|
||||
export const lspSemanticTokensEvent = lspEvent.of(LspAnnotation.SemanticTokens)
|
||||
|
||||
const CompletionItemKindMap = Object.fromEntries(
|
||||
Object.entries(CompletionItemKind).map(([key, value]) => [value, key])
|
||||
) as Record<CompletionItemKind, string>
|
||||
|
||||
const changesDelay = 600
|
||||
|
||||
const addToken = StateEffect.define<SemanticToken>({
|
||||
map: (token: SemanticToken, change) => ({
|
||||
...token,
|
||||
from: change.mapPos(token.from),
|
||||
to: change.mapPos(token.to),
|
||||
}),
|
||||
})
|
||||
|
||||
export const semanticTokenField = StateField.define<DecorationSet>({
|
||||
create() {
|
||||
return Decoration.none
|
||||
},
|
||||
update(highlights, tr) {
|
||||
// Nothing can come before this line, this is very important!
|
||||
// It makes sure the highlights are updated correctly for the changes.
|
||||
highlights = highlights.map(tr.changes)
|
||||
|
||||
const isSemanticTokensEvent = tr.annotation(lspSemanticTokensEvent.type)
|
||||
if (!isSemanticTokensEvent) {
|
||||
return highlights
|
||||
}
|
||||
|
||||
// Check if any of the changes are addToken
|
||||
const hasAddToken = tr.effects.some((e) => e.is(addToken))
|
||||
if (hasAddToken) {
|
||||
highlights = highlights.update({
|
||||
filter: (from, to) => false,
|
||||
})
|
||||
}
|
||||
|
||||
for (const e of tr.effects)
|
||||
if (e.is(addToken)) {
|
||||
const tag = getTag(e.value)
|
||||
const className = tag
|
||||
? highlightingFor(tr.startState, [tag])
|
||||
: undefined
|
||||
|
||||
if (e.value.from < e.value.to && tag) {
|
||||
if (className) {
|
||||
highlights = highlights.update({
|
||||
add: [
|
||||
Decoration.mark({ class: className }).range(
|
||||
e.value.from,
|
||||
e.value.to
|
||||
),
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return highlights
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
})
|
||||
|
||||
export enum TransactionAnnotation {
|
||||
Diagnostics = 'diagnostics',
|
||||
Remote = 'remote',
|
||||
UserSelect = 'user.select',
|
||||
UserInput = 'user.input',
|
||||
UserMove = 'user.move',
|
||||
UserDelete = 'user.delete',
|
||||
UserUndo = 'user.undo',
|
||||
UserRedo = 'user.redo',
|
||||
|
||||
Copoilot = 'copilot',
|
||||
OutsideEditor = 'outsideEditor',
|
||||
CodeManager = 'codeManager',
|
||||
ModelingMachine = 'modelingMachineEvent',
|
||||
LspSemanticTokens = 'lspSemanticTokensEvent',
|
||||
|
||||
PickedCompletion = 'pickedCompletion',
|
||||
}
|
||||
|
||||
export interface TransactionInfo {
|
||||
annotations: TransactionAnnotation[]
|
||||
time: number | null
|
||||
docChanged: boolean
|
||||
addToHistory: boolean
|
||||
inSnippet: boolean
|
||||
}
|
||||
|
||||
export const updateInfo = (update: ViewUpdate): TransactionInfo[] => {
|
||||
let transactionInfos: TransactionInfo[] = []
|
||||
|
||||
for (const tr of update.transactions) {
|
||||
let annotations: TransactionAnnotation[] = []
|
||||
|
||||
if (tr.isUserEvent('select')) {
|
||||
annotations.push(TransactionAnnotation.UserSelect)
|
||||
}
|
||||
|
||||
if (tr.isUserEvent('input')) {
|
||||
annotations.push(TransactionAnnotation.UserInput)
|
||||
}
|
||||
if (tr.isUserEvent('delete')) {
|
||||
annotations.push(TransactionAnnotation.UserDelete)
|
||||
}
|
||||
if (tr.isUserEvent('undo')) {
|
||||
annotations.push(TransactionAnnotation.UserUndo)
|
||||
}
|
||||
if (tr.isUserEvent('redo')) {
|
||||
annotations.push(TransactionAnnotation.UserRedo)
|
||||
}
|
||||
if (tr.isUserEvent('move')) {
|
||||
annotations.push(TransactionAnnotation.UserMove)
|
||||
}
|
||||
|
||||
if (tr.annotation(pickedCompletion) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.PickedCompletion)
|
||||
}
|
||||
|
||||
if (tr.annotation(copilotPluginEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Copoilot)
|
||||
}
|
||||
|
||||
if (tr.annotation(updateOutsideEditorEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.OutsideEditor)
|
||||
}
|
||||
|
||||
if (tr.annotation(codeManagerUpdateEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.CodeManager)
|
||||
}
|
||||
|
||||
if (tr.annotation(modelingMachineEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.ModelingMachine)
|
||||
}
|
||||
|
||||
if (tr.annotation(lspSemanticTokensEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.LspSemanticTokens)
|
||||
}
|
||||
|
||||
if (tr.annotation(setDiagnosticsEvent.type) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Diagnostics)
|
||||
}
|
||||
|
||||
if (tr.annotation(Transaction.remote) !== undefined) {
|
||||
annotations.push(TransactionAnnotation.Remote)
|
||||
}
|
||||
|
||||
transactionInfos.push({
|
||||
annotations,
|
||||
time: tr.annotation(Transaction.time) || null,
|
||||
docChanged: tr.docChanged,
|
||||
addToHistory: tr.annotation(Transaction.addToHistory) || false,
|
||||
inSnippet: hasNextSnippetField(update.state),
|
||||
})
|
||||
}
|
||||
|
||||
return transactionInfos
|
||||
}
|
||||
|
||||
export interface RelevantUpdate {
|
||||
overall: boolean
|
||||
userSelect: boolean
|
||||
time: number | null
|
||||
}
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
time: null,
|
||||
}
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
info.docChanged ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
}
|
||||
changesDelay?: number
|
||||
}
|
||||
|
||||
export class LanguageServerPlugin implements PluginValue {
|
||||
@ -267,6 +70,13 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
|
||||
private previousSemanticTokens: SemanticToken[] = []
|
||||
|
||||
private allowHTMLContent: boolean = true
|
||||
private changesDelay: number = 600
|
||||
private processLspNotification?: (
|
||||
plugin: LanguageServerPlugin,
|
||||
notification: LSP.NotificationMessage
|
||||
) => void
|
||||
|
||||
private _defferer = deferExecution((code: string) => {
|
||||
try {
|
||||
// Update the state (not the editor) with the new code.
|
||||
@ -278,22 +88,29 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
contentChanges: [{ text: code }],
|
||||
})
|
||||
|
||||
this.requestSemanticTokens(this.view)
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}, changesDelay)
|
||||
}, this.changesDelay)
|
||||
|
||||
constructor(
|
||||
client: LanguageServerClient,
|
||||
private view: EditorView,
|
||||
private allowHTMLContent: boolean
|
||||
) {
|
||||
this.client = client
|
||||
constructor(options: LanguageServerOptions, private view: EditorView) {
|
||||
this.client = options.client
|
||||
this.documentVersion = 0
|
||||
|
||||
if (options.changesDelay) {
|
||||
this.changesDelay = options.changesDelay
|
||||
}
|
||||
|
||||
if (options.allowHTMLContent !== undefined) {
|
||||
this.allowHTMLContent = options.allowHTMLContent
|
||||
}
|
||||
|
||||
this.client.attachPlugin(this)
|
||||
|
||||
this.processLspNotification = options.processLspNotification
|
||||
|
||||
this.initialize({
|
||||
documentText: this.getDocText(),
|
||||
})
|
||||
@ -302,6 +119,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
private getDocPath(view = this.view) {
|
||||
return view.state.facet(docPathFacet)
|
||||
}
|
||||
|
||||
private getDocText(view = this.view) {
|
||||
return view.state.doc.toString()
|
||||
}
|
||||
@ -348,7 +166,8 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
},
|
||||
})
|
||||
|
||||
this.requestSemanticTokens(this.view)
|
||||
this.requestSemanticTokens()
|
||||
this.updateFoldingRanges()
|
||||
}
|
||||
|
||||
async sendChange({ documentText }: { documentText: string }) {
|
||||
@ -357,7 +176,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
this._defferer(documentText)
|
||||
}
|
||||
|
||||
requestDiagnostics(view: EditorView) {
|
||||
requestDiagnostics() {
|
||||
this.sendChange({ documentText: this.getDocText() })
|
||||
}
|
||||
|
||||
@ -389,8 +208,8 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
dom.classList.add('documentation')
|
||||
dom.classList.add('hover-tooltip')
|
||||
dom.style.zIndex = '99999999'
|
||||
if (this.allowHTMLContent) dom.innerHTML = formatContents(contents)
|
||||
else dom.textContent = formatContents(contents)
|
||||
if (this.allowHTMLContent) dom.innerHTML = formatMarkdownContents(contents)
|
||||
else dom.textContent = formatMarkdownContents(contents)
|
||||
return { pos, end, create: (view) => ({ dom }), above: true }
|
||||
}
|
||||
|
||||
@ -400,6 +219,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
!this.client.getServerCapabilities().foldingRangeProvider
|
||||
)
|
||||
return null
|
||||
|
||||
const result = await this.client.textDocumentFoldingRange({
|
||||
textDocument: { uri: this.getDocUri() },
|
||||
})
|
||||
@ -442,41 +262,6 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return null
|
||||
}
|
||||
|
||||
async updateUnits(units: UnitLength): Promise<UpdateUnitsResponse | null> {
|
||||
if (this.client.name !== 'kcl') return null
|
||||
if (!this.client.ready) return null
|
||||
|
||||
return await this.client.updateUnits({
|
||||
textDocument: {
|
||||
uri: this.getDocUri(),
|
||||
},
|
||||
text: this.getDocText(),
|
||||
units,
|
||||
})
|
||||
}
|
||||
async updateCanExecute(
|
||||
canExecute: boolean
|
||||
): Promise<UpdateCanExecuteResponse | null> {
|
||||
if (this.client.name !== 'kcl') return null
|
||||
if (!this.client.ready) return null
|
||||
|
||||
let response = await this.client.updateCanExecute({
|
||||
canExecute,
|
||||
})
|
||||
|
||||
if (!canExecute && response.isExecuting) {
|
||||
// We want to wait until the server is not busy before we reply to the
|
||||
// caller.
|
||||
while (response.isExecuting) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
response = await this.client.updateCanExecute({
|
||||
canExecute,
|
||||
})
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
async requestFormatting() {
|
||||
if (
|
||||
!this.client.ready ||
|
||||
@ -504,8 +289,15 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
if (!result) return null
|
||||
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const { newText } = result[i]
|
||||
codeManager.updateCodeStateEditor(newText)
|
||||
const { range, newText } = result[i]
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: posToOffset(this.view.state.doc, range.start)!,
|
||||
to: posToOffset(this.view.state.doc, range.end)!,
|
||||
insert: newText,
|
||||
},
|
||||
annotations: [lspFormatCodeEvent, Transaction.addToHistory.of(true)],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -571,7 +363,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
}
|
||||
if (documentation) {
|
||||
completion.info = () => {
|
||||
const htmlString = formatContents(documentation)
|
||||
const htmlString = formatMarkdownContents(documentation)
|
||||
const htmlNode = document.createElement('div')
|
||||
htmlNode.style.display = 'contents'
|
||||
htmlNode.innerHTML = htmlString
|
||||
@ -649,7 +441,7 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return tokenRanges
|
||||
}
|
||||
|
||||
async requestSemanticTokens(view: EditorView) {
|
||||
async requestSemanticTokens() {
|
||||
if (
|
||||
!this.client.ready ||
|
||||
!this.client.getServerCapabilities().semanticTokensProvider
|
||||
@ -663,14 +455,14 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
if (!result) return null
|
||||
|
||||
const { data } = result
|
||||
this.previousSemanticTokens = this.parseSemanticTokens(view, data)
|
||||
this.previousSemanticTokens = this.parseSemanticTokens(this.view, data)
|
||||
|
||||
const effects: StateEffect<SemanticToken | Extension>[] =
|
||||
this.previousSemanticTokens.map((tokenRange: any) =>
|
||||
addToken.of(tokenRange)
|
||||
)
|
||||
|
||||
view.dispatch({
|
||||
this.view.dispatch({
|
||||
effects,
|
||||
|
||||
annotations: [lspSemanticTokensEvent, Transaction.addToHistory.of(false)],
|
||||
@ -708,25 +500,13 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
notification.params
|
||||
)
|
||||
break
|
||||
case 'kcl/astUpdated':
|
||||
// The server has updated the AST, we should update elsewhere.
|
||||
let updatedAst = notification.params as Program
|
||||
console.log('[lsp]: Updated AST', updatedAst)
|
||||
|
||||
// Update the folding ranges, since the AST has changed.
|
||||
// This is a hack since codemirror does not support async foldService.
|
||||
// When they do we can delete this.
|
||||
this.updateFoldingRanges()
|
||||
break
|
||||
case 'kcl/memoryUpdated':
|
||||
// The server has updated the memory, we should update elsewhere.
|
||||
let updatedMemory = notification.params as ProgramMemory
|
||||
console.log('[lsp]: Updated Memory', updatedMemory)
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
// Send it to the plugin
|
||||
this.processLspNotification?.(this, notification)
|
||||
}
|
||||
|
||||
processDiagnostics(params: PublishDiagnosticsParams) {
|
||||
@ -760,18 +540,26 @@ export class LanguageServerPlugin implements PluginValue {
|
||||
return 0
|
||||
})
|
||||
|
||||
editorManager.addDiagnostics(diagnostics)
|
||||
/* This creates infighting with the others.
|
||||
* TODO: turn it back on when we have a better way to handle it.
|
||||
* this.view.dispatch({
|
||||
effects: [setDiagnosticsEffect.of(diagnostics)],
|
||||
annotations: [lspDiagnosticsEvent, Transaction.addToHistory.of(false)],
|
||||
})*/
|
||||
}
|
||||
}
|
||||
|
||||
function formatContents(
|
||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||
): string {
|
||||
if (Array.isArray(contents)) {
|
||||
return contents.map((c) => formatContents(c) + '\n\n').join('')
|
||||
} else if (typeof contents === 'string') {
|
||||
return Marked.parse(contents)
|
||||
} else {
|
||||
return Marked.parse(contents.value)
|
||||
export class LanguageServerPluginSpec
|
||||
implements PluginSpec<LanguageServerPlugin>
|
||||
{
|
||||
provide(plugin: ViewPlugin<LanguageServerPlugin>): Extension {
|
||||
return [
|
||||
lspAutocompleteKeymapExt,
|
||||
lspFormatExt(plugin),
|
||||
lspHoverExt(plugin),
|
||||
lspIndentExt(),
|
||||
lspLintExt(),
|
||||
lspSemanticTokensExt(),
|
||||
]
|
||||
}
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import { highlightingFor } from '@codemirror/language'
|
||||
import { StateEffect, StateField, Extension } from '@codemirror/state'
|
||||
import { EditorView, Decoration, DecorationSet } from '@codemirror/view'
|
||||
|
||||
import { Tag, tags } from '@lezer/highlight'
|
||||
|
||||
import { lspSemanticTokensEvent } from './annotations'
|
||||
|
||||
export interface SemanticToken {
|
||||
from: number
|
||||
to: number
|
||||
@ -7,6 +13,63 @@ export interface SemanticToken {
|
||||
modifiers: string[]
|
||||
}
|
||||
|
||||
export const addToken = StateEffect.define<SemanticToken>({
|
||||
map: (token: SemanticToken, change) => ({
|
||||
...token,
|
||||
from: change.mapPos(token.from),
|
||||
to: change.mapPos(token.to),
|
||||
}),
|
||||
})
|
||||
|
||||
export default function lspSemanticTokenExt(): Extension {
|
||||
return StateField.define<DecorationSet>({
|
||||
create() {
|
||||
return Decoration.none
|
||||
},
|
||||
update(highlights, tr) {
|
||||
// Nothing can come before this line, this is very important!
|
||||
// It makes sure the highlights are updated correctly for the changes.
|
||||
highlights = highlights.map(tr.changes)
|
||||
|
||||
const isSemanticTokensEvent = tr.annotation(lspSemanticTokensEvent.type)
|
||||
if (!isSemanticTokensEvent) {
|
||||
return highlights
|
||||
}
|
||||
|
||||
// Check if any of the changes are addToken
|
||||
const hasAddToken = tr.effects.some((e) => e.is(addToken))
|
||||
if (hasAddToken) {
|
||||
highlights = highlights.update({
|
||||
filter: (from, to) => false,
|
||||
})
|
||||
}
|
||||
|
||||
for (const e of tr.effects)
|
||||
if (e.is(addToken)) {
|
||||
const tag = getTag(e.value)
|
||||
const className = tag
|
||||
? highlightingFor(tr.startState, [tag])
|
||||
: undefined
|
||||
|
||||
if (e.value.from < e.value.to && tag) {
|
||||
if (className) {
|
||||
highlights = highlights.update({
|
||||
add: [
|
||||
Decoration.mark({ class: className }).range(
|
||||
e.value.from,
|
||||
e.value.to
|
||||
),
|
||||
],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return highlights
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
})
|
||||
}
|
||||
|
||||
export function getTag(semanticToken: SemanticToken): Tag | null {
|
||||
let tokenType = convertSemanticTokenTypeToCodeMirrorTag(semanticToken.type)
|
||||
|
55
packages/codemirror-lsp-client/src/plugin/util.ts
Normal file
55
packages/codemirror-lsp-client/src/plugin/util.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Text } from '@codemirror/state'
|
||||
import { Marked } from '@ts-stack/markdown'
|
||||
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
|
||||
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
||||
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null
|
||||
let latestArgs: T
|
||||
|
||||
function later() {
|
||||
timeout = null
|
||||
func(latestArgs)
|
||||
}
|
||||
|
||||
function deferred(args: T) {
|
||||
latestArgs = args
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
|
||||
return deferred
|
||||
}
|
||||
|
||||
export function posToOffset(
|
||||
doc: Text,
|
||||
pos: { line: number; character: number }
|
||||
): number | undefined {
|
||||
if (pos.line >= doc.lines) return
|
||||
const offset = doc.line(pos.line + 1).from + pos.character
|
||||
if (offset > doc.length) return
|
||||
return offset
|
||||
}
|
||||
|
||||
export function offsetToPos(doc: Text, offset: number) {
|
||||
const line = doc.lineAt(offset)
|
||||
return {
|
||||
line: line.number - 1,
|
||||
character: offset - line.from,
|
||||
}
|
||||
}
|
||||
|
||||
export function formatMarkdownContents(
|
||||
contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[]
|
||||
): string {
|
||||
if (Array.isArray(contents)) {
|
||||
return contents.map((c) => formatMarkdownContents(c) + '\n\n').join('')
|
||||
} else if (typeof contents === 'string') {
|
||||
return Marked.parse(contents)
|
||||
} else {
|
||||
return Marked.parse(contents.value)
|
||||
}
|
||||
}
|
18
packages/codemirror-lsp-client/tsconfig.json
Normal file
18
packages/codemirror-lsp-client/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"module": "commonjs",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src", "./*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
231
packages/codemirror-lsp-client/yarn.lock
Normal file
231
packages/codemirror-lsp-client/yarn.lock
Normal file
@ -0,0 +1,231 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@codemirror/autocomplete@^6.16.3":
|
||||
version "6.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.16.3.tgz#04d5a4e4e44ccae1ba525d47db53a5479bf46338"
|
||||
integrity sha512-Vl/tIeRVVUCRDuOG48lttBasNQu8usGgXQawBXI7WJAiUDSFOfzflmEsZFZo48mAvAaa4FZ/4/yLLxFtdJaKYA==
|
||||
dependencies:
|
||||
"@codemirror/language" "^6.0.0"
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.17.0"
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@codemirror/language@^6.0.0", "@codemirror/language@^6.10.2":
|
||||
version "6.10.2"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/language/-/language-6.10.2.tgz#4056dc219619627ffe995832eeb09cea6060be61"
|
||||
integrity sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==
|
||||
dependencies:
|
||||
"@codemirror/state" "^6.0.0"
|
||||
"@codemirror/view" "^6.23.0"
|
||||
"@lezer/common" "^1.1.0"
|
||||
"@lezer/highlight" "^1.0.0"
|
||||
"@lezer/lr" "^1.0.0"
|
||||
style-mod "^4.0.0"
|
||||
|
||||
"@codemirror/state@^6.0.0", "@codemirror/state@^6.4.0", "@codemirror/state@^6.4.1":
|
||||
version "6.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/state/-/state-6.4.1.tgz#da57143695c056d9a3c38705ed34136e2b68171b"
|
||||
integrity sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==
|
||||
|
||||
"@codemirror/view@^6.17.0", "@codemirror/view@^6.23.0":
|
||||
version "6.28.2"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/view/-/view-6.28.2.tgz#026d5d2bd315aa015c1a1573b6358eeba7acd004"
|
||||
integrity sha512-A3DmyVfjgPsGIjiJqM/zvODUAPQdQl3ci0ghehYNnbt5x+o76xq+dL5+mMBuysDXnI3kapgOkoeJ0sbtL/3qPw==
|
||||
dependencies:
|
||||
"@codemirror/state" "^6.4.0"
|
||||
style-mod "^4.1.0"
|
||||
w3c-keyname "^2.2.4"
|
||||
|
||||
"@cspotcode/source-map-support@^0.8.0":
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
|
||||
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
|
||||
dependencies:
|
||||
"@jridgewell/trace-mapping" "0.3.9"
|
||||
|
||||
"@jridgewell/resolve-uri@^3.0.3":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
|
||||
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@jridgewell/trace-mapping@0.3.9":
|
||||
version "0.3.9"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
|
||||
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
|
||||
dependencies:
|
||||
"@jridgewell/resolve-uri" "^3.0.3"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||
|
||||
"@lezer/common@^1.0.0", "@lezer/common@^1.1.0":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.2.1.tgz#198b278b7869668e1bebbe687586e12a42731049"
|
||||
integrity sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==
|
||||
|
||||
"@lezer/highlight@^1.0.0", "@lezer/highlight@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/highlight/-/highlight-1.2.0.tgz#e5898c3644208b4b589084089dceeea2966f7780"
|
||||
integrity sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@lezer/lr@^1.0.0":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-1.4.1.tgz#fe25f051880a754e820b28148d90aa2a96b8bdd2"
|
||||
integrity sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==
|
||||
dependencies:
|
||||
"@lezer/common" "^1.0.0"
|
||||
|
||||
"@ts-stack/markdown@^1.5.0":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@ts-stack/markdown/-/markdown-1.5.0.tgz#5dc298a20dc3dc040143c5a5948201eb6bf5419d"
|
||||
integrity sha512-ntVX2Kmb2jyTdH94plJohokvDVPvp6CwXHqsa9NVZTK8cOmHDCYNW0j6thIadUVRTStJhxhfdeovLd0owqDxLw==
|
||||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@tsconfig/node10@^1.0.7":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2"
|
||||
integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==
|
||||
|
||||
"@tsconfig/node12@^1.0.7":
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
|
||||
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
|
||||
|
||||
"@tsconfig/node14@^1.0.0":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
|
||||
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
|
||||
|
||||
"@tsconfig/node16@^1.0.2":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@types/node@^20.14.9":
|
||||
version "20.14.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.9.tgz#12e8e765ab27f8c421a1820c99f5f313a933b420"
|
||||
integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
acorn-walk@^8.1.1:
|
||||
version "8.3.3"
|
||||
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e"
|
||||
integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==
|
||||
dependencies:
|
||||
acorn "^8.11.0"
|
||||
|
||||
acorn@^8.11.0, acorn@^8.4.1:
|
||||
version "8.12.0"
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c"
|
||||
integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
|
||||
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
|
||||
|
||||
create-require@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
|
||||
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
json-rpc-2.0@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/json-rpc-2.0/-/json-rpc-2.0-1.7.0.tgz#840deb0bc168463e12bceb462f7fe225e793fc17"
|
||||
integrity sha512-asnLgC1qD5ytP+fvBP8uL0rvj+l8P6iYICbzZ8dVxCpESffVjzA7KkYkbKCIbavs7cllwH1ZUaNtJwphdeRqpg==
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.6"
|
||||
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
|
||||
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
|
||||
|
||||
style-mod@^4.0.0, style-mod@^4.1.0:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/style-mod/-/style-mod-4.1.2.tgz#ca238a1ad4786520f7515a8539d5a63691d7bf67"
|
||||
integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==
|
||||
|
||||
ts-node@^10.9.2:
|
||||
version "10.9.2"
|
||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f"
|
||||
integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==
|
||||
dependencies:
|
||||
"@cspotcode/source-map-support" "^0.8.0"
|
||||
"@tsconfig/node10" "^1.0.7"
|
||||
"@tsconfig/node12" "^1.0.7"
|
||||
"@tsconfig/node14" "^1.0.0"
|
||||
"@tsconfig/node16" "^1.0.2"
|
||||
acorn "^8.4.1"
|
||||
acorn-walk "^8.1.1"
|
||||
arg "^4.1.0"
|
||||
create-require "^1.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
v8-compile-cache-lib "^3.0.1"
|
||||
yn "3.1.1"
|
||||
|
||||
tslib@^2.3.0:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
|
||||
typescript@^5.5.2:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507"
|
||||
integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
v8-compile-cache-lib@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
|
||||
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
|
||||
|
||||
vscode-jsonrpc@8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
|
||||
integrity sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==
|
||||
|
||||
vscode-languageserver-protocol@^3.17.5:
|
||||
version "3.17.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz#864a8b8f390835572f4e13bd9f8313d0e3ac4bea"
|
||||
integrity sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==
|
||||
dependencies:
|
||||
vscode-jsonrpc "8.2.0"
|
||||
vscode-languageserver-types "3.17.5"
|
||||
|
||||
vscode-languageserver-types@3.17.5:
|
||||
version "3.17.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz#3273676f0cf2eab40b3f44d085acbb7f08a39d8a"
|
||||
integrity sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==
|
||||
|
||||
vscode-uri@^3.0.8:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
|
||||
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
|
||||
|
||||
w3c-keyname@^2.2.4:
|
||||
version "2.2.8"
|
||||
resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5"
|
||||
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
@ -1,4 +1,3 @@
|
||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import React, {
|
||||
createContext,
|
||||
@ -7,8 +6,13 @@ import React, {
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||
import Client from '../editor/plugins/lsp/client'
|
||||
import {
|
||||
LanguageServerClient,
|
||||
FromServer,
|
||||
IntoServer,
|
||||
LspWorkerEventType,
|
||||
LanguageServerPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
||||
import KclLanguageSupport from 'editor/plugins/lsp/kcl/language'
|
||||
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
|
||||
@ -21,15 +25,12 @@ import { paths } from 'lib/paths'
|
||||
import { FileEntry } from 'lib/types'
|
||||
import Worker from 'editor/plugins/lsp/worker.ts?worker'
|
||||
import {
|
||||
LspWorkerEventType,
|
||||
KclWorkerOptions,
|
||||
CopilotWorkerOptions,
|
||||
LspWorker,
|
||||
} from 'editor/plugins/lsp/types'
|
||||
import { wasmUrl } from 'lang/wasm'
|
||||
import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { err } from 'lib/trap'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
@ -77,13 +78,11 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
isCopilotLspServerReady,
|
||||
setIsKclLspServerReady,
|
||||
setIsCopilotLspServerReady,
|
||||
isStreamReady,
|
||||
} = useStore((s) => ({
|
||||
isKclLspServerReady: s.isKclLspServerReady,
|
||||
isCopilotLspServerReady: s.isCopilotLspServerReady,
|
||||
setIsKclLspServerReady: s.setIsKclLspServerReady,
|
||||
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const [isLspReady, setIsLspReady] = useState(false)
|
||||
const [isCopilotReady, setIsCopilotReady] = useState(false)
|
||||
@ -98,8 +97,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
} = useSettingsAuthContext()
|
||||
const token = auth?.context.token
|
||||
const navigate = useNavigate()
|
||||
const { overallState } = useNetworkContext()
|
||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||
|
||||
// So this is a bit weird, we need to initialize the lsp server and client.
|
||||
// But the server happens async so we break this into two parts.
|
||||
@ -130,12 +127,15 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const fromServer: FromServer | Error = FromServer.create()
|
||||
if (err(fromServer)) return { lspClient: null }
|
||||
|
||||
const client = new Client(fromServer, intoServer, () => {
|
||||
setIsLspReady(true)
|
||||
const lspClient = new LanguageServerClient({
|
||||
name: LspWorker.Kcl,
|
||||
fromServer,
|
||||
intoServer,
|
||||
initializedCallback: () => {
|
||||
setIsLspReady(true)
|
||||
},
|
||||
})
|
||||
|
||||
const lspClient = new LanguageServerClient({ client, name: LspWorker.Kcl })
|
||||
|
||||
return { lspClient }
|
||||
}, [
|
||||
// We need a token for authenticating the server.
|
||||
@ -168,6 +168,26 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
documentUri: `file:///${PROJECT_ENTRYPOINT}`,
|
||||
workspaceFolders: getWorkspaceFolders(),
|
||||
client: kclLspClient,
|
||||
processLspNotification: (
|
||||
plugin: LanguageServerPlugin,
|
||||
notification: LSP.NotificationMessage
|
||||
) => {
|
||||
try {
|
||||
switch (notification.method) {
|
||||
case 'kcl/astUpdated':
|
||||
// Update the folding ranges, since the AST has changed.
|
||||
// This is a hack since codemirror does not support async foldService.
|
||||
// When they do we can delete this.
|
||||
plugin.updateFoldingRanges()
|
||||
plugin.requestSemanticTokens()
|
||||
break
|
||||
case 'kcl/memoryUpdated':
|
||||
break
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
plugin = lsp
|
||||
@ -175,27 +195,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return plugin
|
||||
}, [kclLspClient, isKclLspServerReady])
|
||||
|
||||
// Re-execute the scene when the units change.
|
||||
useEffect(() => {
|
||||
if (kclLspClient) {
|
||||
let plugins = kclLspClient.plugins
|
||||
for (let plugin of plugins) {
|
||||
if (plugin.updateUnits && isStreamReady && isNetworkOkay) {
|
||||
plugin.updateUnits(defaultUnit.current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
kclLspClient,
|
||||
defaultUnit.current,
|
||||
|
||||
// We want to re-execute the scene if the network comes back online.
|
||||
// The lsp server will only re-execute if there were previous errors or
|
||||
// changes, so it's fine to send it thru here.
|
||||
isStreamReady,
|
||||
isNetworkOkay,
|
||||
])
|
||||
|
||||
const { lspClient: copilotLspClient } = useMemo(() => {
|
||||
if (!token || token === '' || TEST) {
|
||||
return { lspClient: null }
|
||||
@ -221,13 +220,13 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const fromServer: FromServer | Error = FromServer.create()
|
||||
if (err(fromServer)) return { lspClient: null }
|
||||
|
||||
const client = new Client(fromServer, intoServer, () => {
|
||||
setIsCopilotReady(true)
|
||||
})
|
||||
|
||||
const lspClient = new LanguageServerClient({
|
||||
client,
|
||||
name: LspWorker.Copilot,
|
||||
fromServer,
|
||||
intoServer,
|
||||
initializedCallback: () => {
|
||||
setIsCopilotReady(true)
|
||||
},
|
||||
})
|
||||
return { lspClient }
|
||||
}, [token])
|
||||
|
@ -17,18 +17,24 @@ import {
|
||||
Transaction,
|
||||
} from '@codemirror/state'
|
||||
import { completionStatus } from '@codemirror/autocomplete'
|
||||
import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util'
|
||||
import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import {
|
||||
LanguageServerPlugin,
|
||||
TransactionAnnotation,
|
||||
offsetToPos,
|
||||
posToOffset,
|
||||
LanguageServerOptions,
|
||||
LanguageServerClient,
|
||||
docPathFacet,
|
||||
languageId,
|
||||
TransactionInfo,
|
||||
updateInfo,
|
||||
workspaceFolders,
|
||||
RelevantUpdate,
|
||||
} from 'editor/plugins/lsp/plugin'
|
||||
lspPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
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'
|
||||
|
||||
const copilotPluginAnnotation = Annotation.define<null>()
|
||||
export const copilotPluginEvent = copilotPluginAnnotation.of(null)
|
||||
@ -184,7 +190,7 @@ export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
@ -194,17 +200,17 @@ export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
(info: TransactionInfo) =>
|
||||
update.focusChanged ||
|
||||
info.transaction.annotation(copilotPluginEvent.type) !== undefined ||
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove) ||
|
||||
info.annotations.includes(TransactionAnnotation.Copoilot)
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
@ -217,6 +223,8 @@ export class CompletionRequester implements PluginValue {
|
||||
private lastPos: number = 0
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
|
||||
private queuedUids: string[] = []
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
return
|
||||
@ -317,7 +325,7 @@ export class CompletionRequester implements PluginValue {
|
||||
const dUri = state.facet(docPathFacet)
|
||||
|
||||
// Request completion from the server
|
||||
const completionResult = await this.client.getCompletion({
|
||||
const completionResult = await this.getCompletion({
|
||||
doc: {
|
||||
source: state.doc.toString(),
|
||||
tabSize: state.facet(EditorState.tabSize),
|
||||
@ -470,7 +478,7 @@ export class CompletionRequester implements PluginValue {
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(true)],
|
||||
})
|
||||
|
||||
this.client.accept(ghostText.uuid)
|
||||
this.accept(ghostText.uuid)
|
||||
return true
|
||||
}
|
||||
|
||||
@ -498,7 +506,7 @@ export class CompletionRequester implements PluginValue {
|
||||
annotations: [copilotPluginEvent, Transaction.addToHistory.of(false)],
|
||||
})
|
||||
|
||||
this.client.reject()
|
||||
this.reject()
|
||||
return false
|
||||
}
|
||||
|
||||
@ -542,6 +550,39 @@ export class CompletionRequester implements PluginValue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async getCompletion(
|
||||
params: CopilotLspCompletionParams
|
||||
): Promise<CopilotCompletionResponse> {
|
||||
const response: CopilotCompletionResponse = await this.client.requestCustom(
|
||||
'copilot/getCompletions',
|
||||
params
|
||||
)
|
||||
//
|
||||
this.queuedUids = [...response.completions.map((c) => c.uuid)]
|
||||
return response
|
||||
}
|
||||
|
||||
async accept(uuid: string) {
|
||||
const badUids = this.queuedUids.filter((u) => u !== uuid)
|
||||
this.queuedUids = []
|
||||
this.acceptCompletion({ uuid })
|
||||
this.rejectCompletions({ uuids: badUids })
|
||||
}
|
||||
|
||||
async reject() {
|
||||
const badUids = this.queuedUids
|
||||
this.queuedUids = []
|
||||
this.rejectCompletions({ uuids: badUids })
|
||||
}
|
||||
|
||||
acceptCompletion(params: CopilotAcceptCompletionParams) {
|
||||
this.client.notifyCustom('copilot/notifyAccepted', params)
|
||||
}
|
||||
|
||||
rejectCompletions(params: CopilotRejectCompletionParams) {
|
||||
this.client.notifyCustom('copilot/notifyRejected', params)
|
||||
}
|
||||
}
|
||||
|
||||
export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
@ -571,13 +612,7 @@ export const copilotPlugin = (options: LanguageServerOptions): Extension => {
|
||||
})
|
||||
|
||||
return [
|
||||
docPathFacet.of(options.documentUri),
|
||||
languageId.of('kcl'),
|
||||
workspaceFolders.of(options.workspaceFolders),
|
||||
ViewPlugin.define(
|
||||
(view) =>
|
||||
new LanguageServerPlugin(options.client, view, options.allowHTMLContent)
|
||||
),
|
||||
lspPlugin(options),
|
||||
completionPlugin,
|
||||
domHandlers,
|
||||
completionDecoration,
|
||||
|
@ -1,67 +1,27 @@
|
||||
import { Extension } from '@codemirror/state'
|
||||
import { ViewPlugin, PluginValue, ViewUpdate } from '@codemirror/view'
|
||||
import {
|
||||
acceptCompletion,
|
||||
autocompletion,
|
||||
clearSnippet,
|
||||
closeCompletion,
|
||||
hasNextSnippetField,
|
||||
moveCompletionSelection,
|
||||
nextSnippetField,
|
||||
prevSnippetField,
|
||||
startCompletion,
|
||||
} from '@codemirror/autocomplete'
|
||||
import { Extension, EditorState, Prec } from '@codemirror/state'
|
||||
import {
|
||||
ViewPlugin,
|
||||
hoverTooltip,
|
||||
EditorView,
|
||||
keymap,
|
||||
KeyBinding,
|
||||
tooltips,
|
||||
PluginValue,
|
||||
ViewUpdate,
|
||||
} from '@codemirror/view'
|
||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||
import { offsetToPos } from 'editor/plugins/lsp/util'
|
||||
import { LanguageServerOptions } from 'editor/plugins/lsp'
|
||||
import { syntaxTree, indentService, foldService } from '@codemirror/language'
|
||||
import { linter, forEachDiagnostic, Diagnostic } from '@codemirror/lint'
|
||||
import {
|
||||
docPathFacet,
|
||||
LanguageServerPlugin,
|
||||
languageId,
|
||||
workspaceFolders,
|
||||
LanguageServerOptions,
|
||||
updateInfo,
|
||||
TransactionInfo,
|
||||
RelevantUpdate,
|
||||
TransactionAnnotation,
|
||||
} from 'editor/plugins/lsp/plugin'
|
||||
LanguageServerClient,
|
||||
lspPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||
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'
|
||||
|
||||
const changesDelay = 600
|
||||
|
||||
export const kclIndentService = () => {
|
||||
// Match the indentation of the previous line (if present).
|
||||
return indentService.of((context, pos) => {
|
||||
try {
|
||||
const previousLine = context.lineAt(pos, -1)
|
||||
const previousLineText = previousLine.text.replaceAll(
|
||||
'\t',
|
||||
' '.repeat(context.state.tabSize)
|
||||
)
|
||||
const match = previousLineText.match(/^(\s)*/)
|
||||
if (match === null || match.length <= 0) return null
|
||||
return match[0].length
|
||||
} catch (err) {
|
||||
console.error('Error in codemirror indentService', err)
|
||||
}
|
||||
return null
|
||||
})
|
||||
}
|
||||
|
||||
export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
const infos = updateInfo(update)
|
||||
// Make sure we are not in a snippet
|
||||
if (infos.some((info) => info.inSnippet)) {
|
||||
if (infos.some((info: TransactionInfo) => info.inSnippet)) {
|
||||
return {
|
||||
overall: false,
|
||||
userSelect: false,
|
||||
@ -70,15 +30,16 @@ export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
}
|
||||
return {
|
||||
overall: infos.some(
|
||||
(info) =>
|
||||
(info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserInput) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserDelete) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserUndo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserRedo) ||
|
||||
info.annotations.includes(TransactionAnnotation.UserMove)
|
||||
info.annotations.includes(TransactionAnnotation.UserMove) ||
|
||||
info.annotations.includes(TransactionAnnotation.FormatCode)
|
||||
),
|
||||
userSelect: infos.some((info) =>
|
||||
userSelect: infos.some((info: TransactionInfo) =>
|
||||
info.annotations.includes(TransactionAnnotation.UserSelect)
|
||||
),
|
||||
time: infos.length ? infos[0].time : null,
|
||||
@ -88,6 +49,11 @@ export const relevantUpdate = (update: ViewUpdate): RelevantUpdate => {
|
||||
// A view plugin that requests completions from the server after a delay
|
||||
export class KclPlugin implements PluginValue {
|
||||
private viewUpdate: ViewUpdate | null = null
|
||||
private client: LanguageServerClient
|
||||
|
||||
constructor(client: LanguageServerClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
private _deffererCodeUpdate = deferExecution(() => {
|
||||
if (this.viewUpdate === null) {
|
||||
@ -131,154 +97,23 @@ export class KclPlugin implements PluginValue {
|
||||
|
||||
this._deffererCodeUpdate(true)
|
||||
}
|
||||
|
||||
async updateUnits(
|
||||
params: UpdateUnitsParams
|
||||
): Promise<UpdateUnitsResponse | null> {
|
||||
return this.client.requestCustom('kcl/updateUnits', params)
|
||||
}
|
||||
|
||||
async updateCanExecute(
|
||||
params: UpdateCanExecuteParams
|
||||
): Promise<UpdateCanExecuteResponse> {
|
||||
return this.client.requestCustom('kcl/updateCanExecute', params)
|
||||
}
|
||||
}
|
||||
|
||||
export function kclPlugin(options: LanguageServerOptions): Extension {
|
||||
let plugin: LanguageServerPlugin | null = null
|
||||
const viewPlugin = ViewPlugin.define(
|
||||
(view) =>
|
||||
(plugin = new LanguageServerPlugin(
|
||||
options.client,
|
||||
view,
|
||||
options.allowHTMLContent
|
||||
))
|
||||
)
|
||||
|
||||
const kclKeymap: readonly KeyBinding[] = [
|
||||
{
|
||||
key: 'Alt-Shift-f',
|
||||
run: (view: EditorView) => {
|
||||
if (view.plugin === null) return false
|
||||
|
||||
// Get the current plugin from the map.
|
||||
const p = view.plugin(viewPlugin)
|
||||
if (p === null) return false
|
||||
|
||||
p.requestFormatting()
|
||||
return true
|
||||
},
|
||||
},
|
||||
]
|
||||
// Create an extension for the key mappings.
|
||||
const kclKeymapExt = Prec.highest(keymap.computeN([], () => [kclKeymap]))
|
||||
|
||||
const autocompleteKeymap: readonly KeyBinding[] = [
|
||||
{ key: 'Ctrl-Space', run: startCompletion },
|
||||
{
|
||||
key: 'Escape',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (clearSnippet(view)) return true
|
||||
|
||||
return closeCompletion(view)
|
||||
},
|
||||
},
|
||||
{ key: 'ArrowDown', run: moveCompletionSelection(true) },
|
||||
{ key: 'ArrowUp', run: moveCompletionSelection(false) },
|
||||
{ key: 'PageDown', run: moveCompletionSelection(true, 'page') },
|
||||
{ key: 'PageUp', run: moveCompletionSelection(false, 'page') },
|
||||
{ key: 'Enter', run: acceptCompletion },
|
||||
{
|
||||
key: 'Tab',
|
||||
run: (view: EditorView): boolean => {
|
||||
if (hasNextSnippetField(view.state)) {
|
||||
const result = nextSnippetField(view)
|
||||
return result
|
||||
}
|
||||
|
||||
return acceptCompletion(view)
|
||||
},
|
||||
shift: prevSnippetField,
|
||||
},
|
||||
]
|
||||
|
||||
const autocompleteKeymapExt = Prec.highest(
|
||||
keymap.computeN([], () => [autocompleteKeymap])
|
||||
)
|
||||
|
||||
const folding = foldService.of(
|
||||
(state: EditorState, lineStart: number, lineEnd: number) => {
|
||||
if (plugin == null) return null
|
||||
|
||||
// Get the folding ranges from the language server.
|
||||
// Since this is async we directly need to update the folding ranges after.
|
||||
return plugin?.foldingRange(lineStart, lineEnd)
|
||||
}
|
||||
)
|
||||
|
||||
return [
|
||||
docPathFacet.of(options.documentUri),
|
||||
languageId.of('kcl'),
|
||||
workspaceFolders.of(options.workspaceFolders),
|
||||
viewPlugin,
|
||||
ViewPlugin.define((view) => new KclPlugin()),
|
||||
kclKeymapExt,
|
||||
kclIndentService(),
|
||||
hoverTooltip(
|
||||
(view, pos) =>
|
||||
plugin?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ??
|
||||
null
|
||||
),
|
||||
tooltips({
|
||||
position: 'absolute',
|
||||
}),
|
||||
linter((view) => {
|
||||
let diagnostics: Diagnostic[] = []
|
||||
forEachDiagnostic(
|
||||
view.state,
|
||||
(d: Diagnostic, from: number, to: number) => {
|
||||
diagnostics.push(d)
|
||||
}
|
||||
)
|
||||
return diagnostics
|
||||
}),
|
||||
folding,
|
||||
autocompleteKeymapExt,
|
||||
autocompletion({
|
||||
defaultKeymap: false,
|
||||
override: [
|
||||
async (context) => {
|
||||
if (plugin == null) return null
|
||||
|
||||
const { state, pos, explicit } = context
|
||||
|
||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
||||
if (
|
||||
nodeBefore.name === 'BlockComment' ||
|
||||
nodeBefore.name === 'LineComment'
|
||||
)
|
||||
return null
|
||||
|
||||
const line = state.doc.lineAt(pos)
|
||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||
let trigChar: string | undefined
|
||||
if (
|
||||
!explicit &&
|
||||
plugin.client
|
||||
.getServerCapabilities()
|
||||
.completionProvider?.triggerCharacters?.includes(
|
||||
line.text[pos - line.from - 1]
|
||||
)
|
||||
) {
|
||||
trigKind = CompletionTriggerKind.TriggerCharacter
|
||||
trigChar = line.text[pos - line.from - 1]
|
||||
}
|
||||
if (
|
||||
trigKind === CompletionTriggerKind.Invoked &&
|
||||
!context.matchBefore(/\w+$/)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
return await plugin.requestCompletion(
|
||||
context,
|
||||
offsetToPos(state.doc, pos),
|
||||
{
|
||||
triggerKind: trigKind,
|
||||
triggerCharacter: trigChar,
|
||||
}
|
||||
)
|
||||
},
|
||||
],
|
||||
}),
|
||||
lspPlugin(options),
|
||||
ViewPlugin.define(() => new KclPlugin(options.client)),
|
||||
]
|
||||
}
|
||||
|
@ -5,11 +5,13 @@ import {
|
||||
defineLanguageFacet,
|
||||
LanguageSupport,
|
||||
} from '@codemirror/language'
|
||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import {
|
||||
LanguageServerClient,
|
||||
LanguageServerPlugin,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { kclPlugin } from '.'
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import KclParser from './parser'
|
||||
import { semanticTokenField } from '../plugin'
|
||||
|
||||
const data = defineLanguageFacet({
|
||||
// https://codemirror.net/docs/ref/#commands.CommentTokens
|
||||
@ -26,6 +28,10 @@ export interface LanguageOptions {
|
||||
workspaceFolders: LSP.WorkspaceFolder[]
|
||||
documentUri: string
|
||||
client: LanguageServerClient
|
||||
processLspNotification?: (
|
||||
plugin: LanguageServerPlugin,
|
||||
notification: LSP.NotificationMessage
|
||||
) => void
|
||||
}
|
||||
|
||||
class KclLanguage extends Language {
|
||||
@ -35,6 +41,7 @@ class KclLanguage extends Language {
|
||||
workspaceFolders: options.workspaceFolders,
|
||||
allowHTMLContent: true,
|
||||
client: options.client,
|
||||
processLspNotification: options.processLspNotification,
|
||||
})
|
||||
|
||||
const parser = new KclParser()
|
||||
@ -55,6 +62,6 @@ export default class KclLanguageSupport extends LanguageSupport {
|
||||
constructor(options: LanguageOptions) {
|
||||
const lang = new KclLanguage(options)
|
||||
|
||||
super(lang, [semanticTokenField])
|
||||
super(lang)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import { Message } from 'vscode-languageserver-protocol'
|
||||
|
||||
const env = import.meta.env.MODE
|
||||
|
||||
export default class Tracer {
|
||||
static client(message: string): void {
|
||||
// These are really noisy, so we have a special env var for them.
|
||||
if (env === 'lsp_tracing') {
|
||||
console.log('lsp client message', message)
|
||||
}
|
||||
}
|
||||
|
||||
static server(input: string | Message): void {
|
||||
// These are really noisy, so we have a special env var for them.
|
||||
if (env === 'lsp_tracing') {
|
||||
const message: string =
|
||||
typeof input === 'string' ? input : JSON.stringify(input)
|
||||
console.log('lsp server message', message)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { LspWorkerEventType } from '@kittycad/codemirror-lsp-client'
|
||||
|
||||
import { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
|
||||
|
||||
export enum LspWorker {
|
||||
@ -17,11 +19,6 @@ export interface CopilotWorkerOptions {
|
||||
apiBaseUrl: string
|
||||
}
|
||||
|
||||
export enum LspWorkerEventType {
|
||||
Init = 'init',
|
||||
Call = 'call',
|
||||
}
|
||||
|
||||
export interface LspWorkerEvent {
|
||||
eventType: LspWorkerEventType
|
||||
eventData: Uint8Array | KclWorkerOptions | CopilotWorkerOptions
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { Text } from '@codemirror/state'
|
||||
|
||||
export function posToOffset(
|
||||
doc: Text,
|
||||
pos: { line: number; character: number }
|
||||
): number | undefined {
|
||||
if (pos.line >= doc.lines) return
|
||||
const offset = doc.line(pos.line + 1).from + pos.character
|
||||
if (offset > doc.length) return
|
||||
return offset
|
||||
}
|
||||
|
||||
export function offsetToPos(doc: Text, offset: number) {
|
||||
const line = doc.lineAt(offset)
|
||||
return {
|
||||
line: line.number - 1,
|
||||
character: offset - line.from,
|
||||
}
|
||||
}
|
@ -1,4 +1,9 @@
|
||||
import { Codec, FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||
import {
|
||||
Codec,
|
||||
FromServer,
|
||||
IntoServer,
|
||||
LspWorkerEventType,
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
import init, {
|
||||
ServerConfig,
|
||||
@ -7,7 +12,6 @@ import init, {
|
||||
} from 'wasm-lib/pkg/wasm_lib'
|
||||
import * as jsrpc from 'json-rpc-2.0'
|
||||
import {
|
||||
LspWorkerEventType,
|
||||
LspWorkerEvent,
|
||||
LspWorker,
|
||||
KclWorkerOptions,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
|
||||
import { posToOffset } from 'editor/plugins/lsp/util'
|
||||
import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
||||
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||
import { Text } from '@codemirror/state'
|
||||
|
||||
|
@ -98,123 +98,119 @@ export type CommandConfig<
|
||||
export type CommandArgumentConfig<
|
||||
OutputType,
|
||||
C = ContextFrom<AnyStateMachine>
|
||||
> =
|
||||
> = {
|
||||
description?: string
|
||||
required:
|
||||
| boolean
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: C
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
} & (
|
||||
| {
|
||||
description?: string
|
||||
required:
|
||||
| boolean
|
||||
inputType: 'options'
|
||||
options:
|
||||
| CommandArgumentOption<OutputType>[]
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
commandBarContext: {
|
||||
argumentsToSubmit: Record<string, unknown>
|
||||
}, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: C
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
} & (
|
||||
| {
|
||||
inputType: 'options'
|
||||
options:
|
||||
| CommandArgumentOption<OutputType>[]
|
||||
| ((
|
||||
commandBarContext: {
|
||||
argumentsToSubmit: Record<string, unknown>
|
||||
}, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: C
|
||||
) => CommandArgumentOption<OutputType>[])
|
||||
optionsFromContext?: (
|
||||
context: C
|
||||
) => CommandArgumentOption<OutputType>[]
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
||||
| {
|
||||
inputType: 'string'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
| {
|
||||
inputType: 'boolean'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
)
|
||||
) => CommandArgumentOption<OutputType>[])
|
||||
optionsFromContext?: (context: C) => CommandArgumentOption<OutputType>[]
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
||||
| {
|
||||
inputType: 'string'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
| {
|
||||
inputType: 'boolean'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: C
|
||||
) => OutputType)
|
||||
defaultValueFromContext?: (context: C) => OutputType
|
||||
}
|
||||
)
|
||||
|
||||
export type CommandArgument<
|
||||
OutputType,
|
||||
T extends AnyStateMachine = AnyStateMachine
|
||||
> =
|
||||
> = {
|
||||
description?: string
|
||||
required:
|
||||
| boolean
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: InterpreterFrom<T>
|
||||
} & (
|
||||
| {
|
||||
description?: string
|
||||
required:
|
||||
| boolean
|
||||
inputType: Extract<CommandInputType, 'options'>
|
||||
options:
|
||||
| CommandArgumentOption<OutputType>[]
|
||||
| ((
|
||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
commandBarContext: {
|
||||
argumentsToSubmit: Record<string, unknown>
|
||||
}, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: ContextFrom<T>
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: InterpreterFrom<T>
|
||||
} & (
|
||||
| {
|
||||
inputType: Extract<CommandInputType, 'options'>
|
||||
options:
|
||||
| CommandArgumentOption<OutputType>[]
|
||||
| ((
|
||||
commandBarContext: {
|
||||
argumentsToSubmit: Record<string, unknown>
|
||||
}, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: ContextFrom<T>
|
||||
) => CommandArgumentOption<OutputType>[])
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
|
||||
| {
|
||||
inputType: 'string'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
| {
|
||||
inputType: 'boolean'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
)
|
||||
) => CommandArgumentOption<OutputType>[])
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
| {
|
||||
inputType: 'selection'
|
||||
selectionTypes: Selection['type'][]
|
||||
multiple: boolean
|
||||
}
|
||||
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
|
||||
| {
|
||||
inputType: 'string'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
| {
|
||||
inputType: 'boolean'
|
||||
defaultValue?:
|
||||
| OutputType
|
||||
| ((
|
||||
commandBarContext: ContextFrom<typeof commandBarMachine>,
|
||||
machineContext?: ContextFrom<T>
|
||||
) => OutputType)
|
||||
}
|
||||
)
|
||||
|
||||
export type CommandArgumentWithName<
|
||||
OutputType,
|
||||
|
@ -2,6 +2,9 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"@kittycad/codemirror-lsp-client": [
|
||||
"../packages/codemirror-lsp-client/src/index.ts"
|
||||
],
|
||||
"/*": ["src/*"]
|
||||
},
|
||||
"types": [
|
||||
@ -23,10 +26,12 @@
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"composite": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src", "e2e", "./*.ts"],
|
||||
"include": ["src", "e2e", "packages", "./*.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
@ -53,6 +53,11 @@ const config = defineConfig({
|
||||
build: {
|
||||
outDir: 'build',
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@kittycad/codemirror-lsp-client': '/packages/codemirror-lsp-client/src',
|
||||
},
|
||||
},
|
||||
plugins: [react(), viteTsconfigPaths(), eslint(), version()],
|
||||
worker: {
|
||||
plugins: () => [viteTsconfigPaths()],
|
||||
|
@ -6687,7 +6687,7 @@ prettier@2.8.1:
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.1.tgz#4e1fd11c34e2421bc1da9aea9bd8127cd0a35efc"
|
||||
integrity sha512-lqGoSJBQNJidqCHE80vqZJHWHRFoNYsSpP9AjFhlhi9ODCJA541svILes/+/1GM3VaL/abZi7cpFzOpdR9UPKg==
|
||||
|
||||
prettier@^2.8.0, prettier@^2.8.8:
|
||||
prettier@^2.8.8:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
|
Reference in New Issue
Block a user