Skip to content

Commit

Permalink
get definition by textDocument/documentLink (#1174)
Browse files Browse the repository at this point in the history
  • Loading branch information
nighca authored Dec 26, 2024
1 parent 481a555 commit 4308b3d
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 52 deletions.
69 changes: 25 additions & 44 deletions spx-gui/src/components/editor/code-editor/code-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import {
type DiagnosticsContext,
type IDiagnosticsProvider,
type IResourceReferencesProvider,
type ResourceReference,
type ResourceReferencesContext,
builtInCommandCopilotExplain,
ChatExplainKind,
type ChatExplainTargetCodeSegment,
builtInCommandCopilotReview,
builtInCommandGoToDefinition,
type HoverContext,
Expand All @@ -44,9 +42,9 @@ import {
type Range,
type TextDocumentIdentifier,
type ResourceIdentifier,
type ResourceReference,
fromLSPTextEdit,
textDocumentId2ResourceModelId,
parseDefinitionId,
type Position,
type Selection,
type CommandArgs,
Expand All @@ -55,7 +53,6 @@ import {
} from './common'
import * as spxDocumentationItems from './document-base/spx'
import * as gopDocumentationItems from './document-base/gop'
import { isDocumentLinkForResourceReference } from './lsp/spxls/methods'
import { TextDocument, createTextDocument } from './text-document'
import { type Monaco } from './monaco'

Expand All @@ -67,28 +64,15 @@ const allItems = Object.values({

class ResourceReferencesProvider
extends Emitter<{
didChangeResourceReferences: []
didChangeResourceReferences: [] // TODO
}>
implements IResourceReferencesProvider
{
constructor(private lspClient: SpxLSPClient) {
super()
}
async provideResourceReferences(ctx: ResourceReferencesContext): Promise<ResourceReference[]> {
const result = await this.lspClient.textDocumentDocumentLink({
textDocument: ctx.textDocument.id
})
if (result == null) return []
const rrs: ResourceReference[] = []
for (const documentLink of result) {
if (!isDocumentLinkForResourceReference(documentLink)) continue
rrs.push({
kind: documentLink.data.kind,
range: fromLSPRange(documentLink.range),
resource: { uri: documentLink.target }
})
}
return rrs
return this.lspClient.getResourceReferences(ctx.textDocument.id)
}
}

Expand Down Expand Up @@ -160,13 +144,10 @@ class HoverProvider implements IHoverProvider {
private documentBase: DocumentBase
) {}

private async getExplainAction(lspHover: lsp.Hover) {
private async getExplainAction(textDocument: TextDocumentIdentifier, position: Position) {
let definition: DefinitionDocumentationItem | null = null
if (!lsp.MarkupContent.is(lspHover.contents)) return null
// TODO: get definition ID from LS `textDocument/documentLink`
const matched = lspHover.contents.value.match(/def-id="([^"]+)"/)
if (matched == null) return null
const defId = parseDefinitionId(matched[1])
const defId = await this.lspClient.getDefinition(textDocument, position)
if (defId == null) return null
definition = await this.documentBase.getDocumentation(defId)
if (definition == null) return null
return {
Expand Down Expand Up @@ -236,7 +217,7 @@ class HoverProvider implements IHoverProvider {
let range: Range | undefined = undefined
if (lspHover.range != null) range = fromLSPRange(lspHover.range)
const maybeActions = await Promise.all([
this.getExplainAction(lspHover),
this.getExplainAction(ctx.textDocument.id, position),
this.getGoToDefinitionAction(position, lspParams),
this.getRenameAction(ctx, position, lspParams)
])
Expand All @@ -246,25 +227,25 @@ class HoverProvider implements IHoverProvider {
}

class ContextMenuProvider implements IContextMenuProvider {
constructor(private lspClient: SpxLSPClient) {}

private getExplainMenuItemForPosition({ textDocument }: ContextMenuContext, position: Position) {
const word = textDocument.getWordAtPosition(position)
if (word == null) return null
const wordStart = { ...position, column: word.startColumn }
const wordEnd = { ...position, column: word.endColumn }
const explainTarget: ChatExplainTargetCodeSegment = {
kind: ChatExplainKind.CodeSegment,
codeSegment: {
// TODO: use definition info from LS and explain definition instead of code-segment
textDocument: textDocument.id,
range: { start: wordStart, end: wordEnd },
content: word.word
}
}
constructor(
private lspClient: SpxLSPClient,
private documentBase: DocumentBase
) {}

private async getExplainMenuItemForPosition({ textDocument }: ContextMenuContext, position: Position) {
const defId = await this.lspClient.getDefinition(textDocument.id, position)
if (defId == null) return null
const definition = await this.documentBase.getDocumentation(defId)
if (definition == null) return null
return {
command: builtInCommandCopilotExplain,
arguments: [explainTarget] satisfies CommandArgs<typeof builtInCommandCopilotExplain>
arguments: [
{
kind: ChatExplainKind.Definition,
overview: definition.overview,
definition: definition.definition
}
] satisfies CommandArgs<typeof builtInCommandCopilotExplain>
}
}

Expand Down Expand Up @@ -474,7 +455,7 @@ export class CodeEditor extends Disposable {
}
})

ui.registerContextMenuProvider(new ContextMenuProvider(this.lspClient))
ui.registerContextMenuProvider(new ContextMenuProvider(lspClient, documentBase))
ui.registerCopilot(copilot)
ui.registerDiagnosticsProvider(this.diagnosticsProvider)
ui.registerHoverProvider(this.hoverProvider)
Expand Down
6 changes: 6 additions & 0 deletions spx-gui/src/components/editor/code-editor/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ export type ResourceIdentifier = {
uri: ResourceURI
}

export type ResourceReference = {
kind: ResourceReferenceKind
range: Range
resource: ResourceIdentifier
}

export type TextDocumentIdentifier = {
/**
* URI of the text document. Examples:
Expand Down
39 changes: 39 additions & 0 deletions spx-gui/src/components/editor/code-editor/lsp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ import { toText } from '@/models/common/file'
import type { Project } from '@/models/project'
import wasmExecScriptUrl from '@/assets/wasm_exec.js?url'
import spxlsWasmUrl from '@/assets/spxls.wasm?url'
import {
fromLSPRange,
type DefinitionIdentifier,
type Position,
type ResourceReference,
type TextDocumentIdentifier,
containsPosition
} from '../common'
import { Spxlc } from './spxls/client'
import type { Files as SpxlsFiles } from './spxls'
import { spxGetDefinitions, spxRenameResources } from './spxls/commands'
import { isDocumentLinkForResourceReference, parseDocumentLinkForDefinition } from './spxls/methods'

function loadScript(url: string) {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -142,4 +151,34 @@ export class SpxLSPClient extends Disposable {
const spxlc = await this.prepareRequest()
return spxlc.request<lsp.TextEdit[] | null>(lsp.DocumentFormattingRequest.method, params)
}

// Higher-level APIs

async getResourceReferences(textDocument: TextDocumentIdentifier): Promise<ResourceReference[]> {
const documentLinks = await this.textDocumentDocumentLink({ textDocument })
if (documentLinks == null) return []
const rrs: ResourceReference[] = []
for (const documentLink of documentLinks) {
if (!isDocumentLinkForResourceReference(documentLink)) continue
rrs.push({
kind: documentLink.data.kind,
range: fromLSPRange(documentLink.range),
resource: { uri: documentLink.target }
})
}
return rrs
}

async getDefinition(textDocument: TextDocumentIdentifier, position: Position): Promise<DefinitionIdentifier | null> {
const documentLinks = await this.textDocumentDocumentLink({ textDocument })
if (documentLinks == null) return null
for (const documentLink of documentLinks) {
const definition = parseDocumentLinkForDefinition(documentLink)
if (definition == null) continue
const range = fromLSPRange(documentLink.range)
if (!containsPosition(range, position)) continue
return definition
}
return null
}
}
17 changes: 16 additions & 1 deletion spx-gui/src/components/editor/code-editor/lsp/spxls/methods.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type * as lsp from 'vscode-languageserver-protocol'
import { isResourceUri, type ResourceReferenceKind } from '../../common'
import { isResourceUri, parseDefinitionId, type DefinitionIdentifier, type ResourceReferenceKind } from '../../common'

export type DocumentLinkForResourceReference = {
range: lsp.Range
Expand All @@ -13,3 +13,18 @@ export type DocumentLinkForResourceReference = {
export function isDocumentLinkForResourceReference(link: lsp.DocumentLink): link is DocumentLinkForResourceReference {
return link.target != null && isResourceUri(link.target)
}

export type DocumentLinkForDefinition = {
range: lsp.Range
/** Definition identifier string */
target: string
}

export function parseDocumentLinkForDefinition(link: lsp.DocumentLink): DefinitionIdentifier | null {
if (link.target == null) return null
try {
return parseDefinitionId(link.target)
} catch {
return null
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { computed, shallowRef, watch } from 'vue'
import Emitter from '@/utils/emitter'
import { ResourceReferenceKind, type BaseContext, type Range, type ResourceIdentifier } from '../../common'
import { ResourceReferenceKind, type BaseContext, type ResourceReference } from '../../common'
import type { TextDocument } from '../../text-document'
import type { monaco } from '../../monaco'
import { toMonacoPosition, toMonacoRange } from '../common'
Expand All @@ -11,12 +11,6 @@ import { createResourceSelector } from './selector'

export type ResourceReferencesContext = BaseContext

export type ResourceReference = {
kind: ResourceReferenceKind
range: Range
resource: ResourceIdentifier
}

export type InternalResourceReference = ResourceReference & {
id: string
}
Expand Down

0 comments on commit 4308b3d

Please sign in to comment.