diff --git a/package-lock.json b/package-lock.json index def58d4..37ea2d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "miller", - "version": "0.2.2", + "version": "0.2.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "miller", - "version": "0.2.2", + "version": "0.2.3", "license": "GPL-3.0-or-later", "dependencies": { "chokidar": "^5.0.0", diff --git a/package.json b/package.json index 5076a8c..ca8fcf9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "miller", "productName": "miller", - "version": "0.2.2", + "version": "0.2.3", "description": "Column-based code editor", "main": ".vite/build/main.js", "scripts": { diff --git a/src/app/editor.ts b/src/app/editor.ts index 7543ad5..3e62f5b 100644 --- a/src/app/editor.ts +++ b/src/app/editor.ts @@ -41,9 +41,9 @@ import { OpenFile } from "./filestate"; import { findReferencesKeymap, formatKeymap, - jumpToDefinitionKeymap, renameKeymap, } from "@codemirror/lsp-client"; +import { jumpToDefinitionKeymap } from "./lsp/definition"; const fixedHeightEditor = EditorView.theme({ "&": { diff --git a/src/app/editorgrid.ts b/src/app/editorgrid.ts index c8b12ea..09342fa 100644 --- a/src/app/editorgrid.ts +++ b/src/app/editorgrid.ts @@ -77,10 +77,11 @@ const EditorWrapper = ( const editors: Displayable[][] = vanX.reactive([[]]); const currentTab = van.state(0); -export function addEditor(file: OpenFile) { +export function addEditor(file: OpenFile): Editor { const editor = file.createEditor(); editors[currentTab.val].push(vanX.noreactive(editor)); editor.focus(); + return editor; } export function addTab() { diff --git a/src/app/lsp.ts b/src/app/lsp.ts index 09d96bd..d03cbb2 100644 --- a/src/app/lsp.ts +++ b/src/app/lsp.ts @@ -1,7 +1,7 @@ // Minimal LSP integration helper for the editor. // Keeps all LSP-specific logic in one place so it's easy to review. -import { Extension, ChangeSet, TransactionSpec } from "@codemirror/state"; +import { Extension, TransactionSpec } from "@codemirror/state"; import { EditorView } from "@codemirror/view"; import { diff --git a/src/app/lsp/definition.ts b/src/app/lsp/definition.ts new file mode 100644 index 0000000..8342d30 --- /dev/null +++ b/src/app/lsp/definition.ts @@ -0,0 +1,81 @@ +import type * as lsp from "vscode-languageserver-protocol" +import { EditorView, Command, KeyBinding } from "@codemirror/view" +import { LSPPlugin } from "@codemirror/lsp-client"; + +import { addEditor } from "../editorgrid"; +import { OpenFile } from "../filestate"; + +function getDefinition(plugin: LSPPlugin, pos: number) { + return plugin.client.request("textDocument/definition", { + textDocument: { uri: plugin.uri }, + position: plugin.toPosition(pos) + }) +} + +function getDeclaration(plugin: LSPPlugin, pos: number) { + return plugin.client.request("textDocument/declaration", { + textDocument: { uri: plugin.uri }, + position: plugin.toPosition(pos) + }) +} + +function getTypeDefinition(plugin: LSPPlugin, pos: number) { + return plugin.client.request("textDocument/typeDefinition", { + textDocument: { uri: plugin.uri }, + position: plugin.toPosition(pos) + }) +} + +function getImplementation(plugin: LSPPlugin, pos: number) { + return plugin.client.request("textDocument/implementation", { + textDocument: { uri: plugin.uri }, + position: plugin.toPosition(pos) + }) +} + +function jumpToOrigin(view: EditorView, type: { get: typeof getDefinition, capability: keyof lsp.ServerCapabilities }): boolean { + const plugin = LSPPlugin.get(view); + const hasCapability = plugin.client.serverCapabilities ? !!plugin.client.serverCapabilities[type.capability] : null; + if (!plugin || !hasCapability) return false + plugin.client.sync() + plugin.client.withMapping(mapping => type.get(plugin, view.state.selection.main.head).then(async response => { + if (!response) return + let loc = Array.isArray(response) ? response[0] : response; + const path = new URL(loc.uri).pathname; + const target = addEditor(await OpenFile.openFile(path)); + const pos = mapping.getMapping(loc.uri) ? mapping.mapPosition(loc.uri, loc.range.start) : plugin.fromPosition(loc.range.start, target.view.state.doc); + target.view.dispatch({selection: {anchor: pos}, scrollIntoView: true, userEvent: "select.definition"}); + }, error => plugin.reportError("Find definition failed", error))) + return true +} + +/// Jump to the definition of the symbol at the cursor. To support +/// cross-file jumps, you'll need to implement +/// [`Workspace.displayFile`](#lsp-client.Workspace.displayFile). +export const jumpToDefinition: Command = view => jumpToOrigin(view, { + get: getDefinition, + capability: "definitionProvider" +}) + +/// Jump to the declaration of the symbol at the cursor. +export const jumpToDeclaration: Command = view => jumpToOrigin(view, { + get: getDeclaration, + capability: "declarationProvider" +}) + +/// Jump to the type definition of the symbol at the cursor. +export const jumpToTypeDefinition: Command = view => jumpToOrigin(view, { + get: getTypeDefinition, + capability: "typeDefinitionProvider" +}) + +/// Jump to the implementation of the symbol at the cursor. +export const jumpToImplementation: Command = view => jumpToOrigin(view, { + get: getImplementation, + capability: "implementationProvider" +}) + +/// Binds F12 to [`jumpToDefinition`](#lsp-client.jumpToDefinition). +export const jumpToDefinitionKeymap: readonly KeyBinding[] = [ + { key: "F12", run: jumpToDefinition, preventDefault: true }, +] diff --git a/src/app/lsp/diagnostics.ts b/src/app/lsp/diagnostics.ts index fc2a706..64a4579 100644 --- a/src/app/lsp/diagnostics.ts +++ b/src/app/lsp/diagnostics.ts @@ -1,5 +1,4 @@ import type * as lsp from "vscode-languageserver-protocol"; -import { ChangeSet } from "@codemirror/state"; import { ViewPlugin, ViewUpdate } from "@codemirror/view"; import { LSPPlugin, LSPClientExtension } from "@codemirror/lsp-client"; import { OpenFile } from "../filestate";