Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use definition from textDocument/documentLink #1174

Merged
merged 1 commit into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个跟早上提到的 diagnostics 类似,如果 editor 总是在合适的时候去拉的话,通过事件主动推也就没什么必要了;这里定义先留着,不着急实现

}>
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
Loading