From 2d92d5d03e31c14d6094df38cb289cf74c97fadc Mon Sep 17 00:00:00 2001 From: Quinten Kock Date: Sat, 6 Dec 2025 22:22:10 +0100 Subject: [PATCH] Open definitions in new column --- src/app/editor.ts | 2 +- src/app/editorgrid.ts | 3 +- src/app/lsp.ts | 2 +- src/app/lsp/definition.ts | 81 ++++++++++++++++++++++++++++++++++++++ src/app/lsp/diagnostics.ts | 1 - 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/app/lsp/definition.ts 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";