refactor(lsp): remove duplicate LSP tools already provided by OpenCode
Remove lsp_goto_definition, lsp_find_references, lsp_symbols as they duplicate OpenCode's built-in LspGotoDefinition, LspFindReferences, LspDocumentSymbols, and LspWorkspaceSymbols. Keep oh-my-opencode-specific tools: - lsp_diagnostics (OpenCode lacks pull diagnostics) - lsp_servers (server management) - lsp_prepare_rename / lsp_rename (OpenCode lacks rename) Clean up associated dead code: - Client methods: hover, definition, references, symbols, codeAction - Utils: formatLocation, formatSymbolKind, formatDocumentSymbol, etc. - Types: Location, LocationLink, SymbolInfo, DocumentSymbol, etc. - Constants: SYMBOL_KIND_MAP, DEFAULT_MAX_REFERENCES/SYMBOLS -418 lines removed.
This commit is contained in:
@@ -12,8 +12,6 @@ const TRUNCATABLE_TOOLS = [
|
||||
"glob",
|
||||
"Glob",
|
||||
"safe_glob",
|
||||
"lsp_find_references",
|
||||
"lsp_symbols",
|
||||
"lsp_diagnostics",
|
||||
"ast_grep_search",
|
||||
"interactive_bash",
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
lsp_goto_definition,
|
||||
lsp_find_references,
|
||||
lsp_symbols,
|
||||
lsp_diagnostics,
|
||||
lsp_servers,
|
||||
lsp_prepare_rename,
|
||||
@@ -56,9 +53,6 @@ export function createBackgroundTools(manager: BackgroundManager, client: Openco
|
||||
}
|
||||
|
||||
export const builtinTools: Record<string, ToolDefinition> = {
|
||||
lsp_goto_definition,
|
||||
lsp_find_references,
|
||||
lsp_symbols,
|
||||
lsp_diagnostics,
|
||||
lsp_servers,
|
||||
lsp_prepare_rename,
|
||||
|
||||
@@ -509,46 +509,6 @@ export class LSPClient {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
}
|
||||
|
||||
async hover(filePath: string, line: number, character: number): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/hover", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
})
|
||||
}
|
||||
|
||||
async definition(filePath: string, line: number, character: number): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/definition", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
})
|
||||
}
|
||||
|
||||
async references(filePath: string, line: number, character: number, includeDeclaration = true): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/references", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
position: { line: line - 1, character },
|
||||
context: { includeDeclaration },
|
||||
})
|
||||
}
|
||||
|
||||
async documentSymbols(filePath: string): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/documentSymbol", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
})
|
||||
}
|
||||
|
||||
async workspaceSymbols(query: string): Promise<unknown> {
|
||||
return this.send("workspace/symbol", { query })
|
||||
}
|
||||
|
||||
async diagnostics(filePath: string): Promise<{ items: Diagnostic[] }> {
|
||||
const absPath = resolve(filePath)
|
||||
const uri = pathToFileURL(absPath).href
|
||||
@@ -587,33 +547,6 @@ export class LSPClient {
|
||||
})
|
||||
}
|
||||
|
||||
async codeAction(
|
||||
filePath: string,
|
||||
startLine: number,
|
||||
startChar: number,
|
||||
endLine: number,
|
||||
endChar: number,
|
||||
only?: string[]
|
||||
): Promise<unknown> {
|
||||
const absPath = resolve(filePath)
|
||||
await this.openFile(absPath)
|
||||
return this.send("textDocument/codeAction", {
|
||||
textDocument: { uri: pathToFileURL(absPath).href },
|
||||
range: {
|
||||
start: { line: startLine - 1, character: startChar },
|
||||
end: { line: endLine - 1, character: endChar },
|
||||
},
|
||||
context: {
|
||||
diagnostics: [],
|
||||
only,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async codeActionResolve(codeAction: unknown): Promise<unknown> {
|
||||
return this.send("codeAction/resolve", codeAction)
|
||||
}
|
||||
|
||||
isAlive(): boolean {
|
||||
return this.proc !== null && !this.processExited && this.proc.exitCode === null
|
||||
}
|
||||
|
||||
@@ -1,34 +1,5 @@
|
||||
import type { LSPServerConfig } from "./types"
|
||||
|
||||
export const SYMBOL_KIND_MAP: Record<number, string> = {
|
||||
1: "File",
|
||||
2: "Module",
|
||||
3: "Namespace",
|
||||
4: "Package",
|
||||
5: "Class",
|
||||
6: "Method",
|
||||
7: "Property",
|
||||
8: "Field",
|
||||
9: "Constructor",
|
||||
10: "Enum",
|
||||
11: "Interface",
|
||||
12: "Function",
|
||||
13: "Variable",
|
||||
14: "Constant",
|
||||
15: "String",
|
||||
16: "Number",
|
||||
17: "Boolean",
|
||||
18: "Array",
|
||||
19: "Object",
|
||||
20: "Key",
|
||||
21: "Null",
|
||||
22: "EnumMember",
|
||||
23: "Struct",
|
||||
24: "Event",
|
||||
25: "Operator",
|
||||
26: "TypeParameter",
|
||||
}
|
||||
|
||||
export const SEVERITY_MAP: Record<number, string> = {
|
||||
1: "error",
|
||||
2: "warning",
|
||||
@@ -36,8 +7,6 @@ export const SEVERITY_MAP: Record<number, string> = {
|
||||
4: "hint",
|
||||
}
|
||||
|
||||
export const DEFAULT_MAX_REFERENCES = 200
|
||||
export const DEFAULT_MAX_SYMBOLS = 200
|
||||
export const DEFAULT_MAX_DIAGNOSTICS = 200
|
||||
|
||||
export const LSP_INSTALL_HINTS: Record<string, string> = {
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
||||
import { getAllServers } from "./config"
|
||||
import {
|
||||
DEFAULT_MAX_REFERENCES,
|
||||
DEFAULT_MAX_SYMBOLS,
|
||||
DEFAULT_MAX_DIAGNOSTICS,
|
||||
} from "./constants"
|
||||
import {
|
||||
withLspClient,
|
||||
formatLocation,
|
||||
formatDocumentSymbol,
|
||||
formatSymbolInfo,
|
||||
formatDiagnostic,
|
||||
filterDiagnosticsBySeverity,
|
||||
formatPrepareRenameResult,
|
||||
@@ -17,157 +12,14 @@ import {
|
||||
formatApplyResult,
|
||||
} from "./utils"
|
||||
import type {
|
||||
Location,
|
||||
LocationLink,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
Diagnostic,
|
||||
PrepareRenameResult,
|
||||
PrepareRenameDefaultBehavior,
|
||||
WorkspaceEdit,
|
||||
} from "./types"
|
||||
|
||||
|
||||
|
||||
export const lsp_goto_definition: ToolDefinition = tool({
|
||||
description: "Jump to symbol definition. Find WHERE something is defined.",
|
||||
args: {
|
||||
filePath: tool.schema.string(),
|
||||
line: tool.schema.number().min(1).describe("1-based"),
|
||||
character: tool.schema.number().min(0).describe("0-based"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.definition(args.filePath, args.line, args.character)) as
|
||||
| Location
|
||||
| Location[]
|
||||
| LocationLink[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const locations = Array.isArray(result) ? result : [result]
|
||||
if (locations.length === 0) {
|
||||
const output = "No definition found"
|
||||
return output
|
||||
}
|
||||
|
||||
const output = locations.map(formatLocation).join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_find_references: ToolDefinition = tool({
|
||||
description: "Find ALL usages/references of a symbol across the entire workspace.",
|
||||
args: {
|
||||
filePath: tool.schema.string(),
|
||||
line: tool.schema.number().min(1).describe("1-based"),
|
||||
character: tool.schema.number().min(0).describe("0-based"),
|
||||
includeDeclaration: tool.schema.boolean().optional().describe("Include the declaration itself"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.references(args.filePath, args.line, args.character, args.includeDeclaration ?? true)) as
|
||||
| Location[]
|
||||
| null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
const output = "No references found"
|
||||
return output
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const truncated = total > DEFAULT_MAX_REFERENCES
|
||||
const limited = truncated ? result.slice(0, DEFAULT_MAX_REFERENCES) : result
|
||||
const lines = limited.map(formatLocation)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} references (showing first ${DEFAULT_MAX_REFERENCES}):`)
|
||||
}
|
||||
const output = lines.join("\n")
|
||||
return output
|
||||
} catch (e) {
|
||||
const output = `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
return output
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
export const lsp_symbols: ToolDefinition = tool({
|
||||
description: "Get symbols from file (document) or search across workspace. Use scope='document' for file outline, scope='workspace' for project-wide symbol search.",
|
||||
args: {
|
||||
filePath: tool.schema.string().describe("File path for LSP context"),
|
||||
scope: tool.schema.enum(["document", "workspace"]).default("document").describe("'document' for file symbols, 'workspace' for project-wide search"),
|
||||
query: tool.schema.string().optional().describe("Symbol name to search (required for workspace scope)"),
|
||||
limit: tool.schema.number().optional().describe("Max results (default 50)"),
|
||||
},
|
||||
execute: async (args, context) => {
|
||||
try {
|
||||
const scope = args.scope ?? "document"
|
||||
|
||||
if (scope === "workspace") {
|
||||
if (!args.query) {
|
||||
return "Error: 'query' is required for workspace scope"
|
||||
}
|
||||
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.workspaceSymbols(args.query!)) as SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = result.slice(0, limit)
|
||||
const lines = limited.map(formatSymbolInfo)
|
||||
if (truncated) {
|
||||
lines.unshift(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
return lines.join("\n")
|
||||
} else {
|
||||
const result = await withLspClient(args.filePath, async (client) => {
|
||||
return (await client.documentSymbols(args.filePath)) as DocumentSymbol[] | SymbolInfo[] | null
|
||||
})
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
return "No symbols found"
|
||||
}
|
||||
|
||||
const total = result.length
|
||||
const limit = Math.min(args.limit ?? DEFAULT_MAX_SYMBOLS, DEFAULT_MAX_SYMBOLS)
|
||||
const truncated = total > limit
|
||||
const limited = truncated ? result.slice(0, limit) : result
|
||||
|
||||
const lines: string[] = []
|
||||
if (truncated) {
|
||||
lines.push(`Found ${total} symbols (showing first ${limit}):`)
|
||||
}
|
||||
|
||||
if ("range" in limited[0]) {
|
||||
lines.push(...(limited as DocumentSymbol[]).map((s) => formatDocumentSymbol(s)))
|
||||
} else {
|
||||
lines.push(...(limited as SymbolInfo[]).map(formatSymbolInfo))
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
} catch (e) {
|
||||
return `Error: ${e instanceof Error ? e.message : String(e)}`
|
||||
}
|
||||
},
|
||||
})
|
||||
// NOTE: lsp_goto_definition, lsp_find_references, lsp_symbols are removed
|
||||
// as they duplicate OpenCode's built-in LSP tools (LspGotoDefinition, LspFindReferences, LspDocumentSymbols, LspWorkspaceSymbols)
|
||||
|
||||
export const lsp_diagnostics: ToolDefinition = tool({
|
||||
description: "Get errors, warnings, hints from language server BEFORE running build.",
|
||||
|
||||
@@ -17,33 +17,6 @@ export interface Range {
|
||||
end: Position
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
uri: string
|
||||
range: Range
|
||||
}
|
||||
|
||||
export interface LocationLink {
|
||||
targetUri: string
|
||||
targetRange: Range
|
||||
targetSelectionRange: Range
|
||||
originSelectionRange?: Range
|
||||
}
|
||||
|
||||
export interface SymbolInfo {
|
||||
name: string
|
||||
kind: number
|
||||
location: Location
|
||||
containerName?: string
|
||||
}
|
||||
|
||||
export interface DocumentSymbol {
|
||||
name: string
|
||||
kind: number
|
||||
range: Range
|
||||
selectionRange: Range
|
||||
children?: DocumentSymbol[]
|
||||
}
|
||||
|
||||
export interface Diagnostic {
|
||||
range: Range
|
||||
severity?: number
|
||||
@@ -52,14 +25,6 @@ export interface Diagnostic {
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface HoverResult {
|
||||
contents:
|
||||
| { kind?: string; value: string }
|
||||
| string
|
||||
| Array<{ kind?: string; value: string } | string>
|
||||
range?: Range
|
||||
}
|
||||
|
||||
export interface TextDocumentIdentifier {
|
||||
uri: string
|
||||
}
|
||||
@@ -111,31 +76,6 @@ export interface PrepareRenameDefaultBehavior {
|
||||
defaultBehavior: boolean
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
title: string
|
||||
command: string
|
||||
arguments?: unknown[]
|
||||
}
|
||||
|
||||
export interface CodeActionContext {
|
||||
diagnostics: Diagnostic[]
|
||||
only?: string[]
|
||||
triggerKind?: CodeActionTriggerKind
|
||||
}
|
||||
|
||||
export type CodeActionTriggerKind = 1 | 2
|
||||
|
||||
export interface CodeAction {
|
||||
title: string
|
||||
kind?: string
|
||||
diagnostics?: Diagnostic[]
|
||||
isPreferred?: boolean
|
||||
disabled?: { reason: string }
|
||||
edit?: WorkspaceEdit
|
||||
command?: Command
|
||||
data?: unknown
|
||||
}
|
||||
|
||||
export interface ServerLookupInfo {
|
||||
id: string
|
||||
command: string[]
|
||||
|
||||
@@ -3,21 +3,14 @@ import { fileURLToPath } from "node:url"
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs"
|
||||
import { LSPClient, lspManager } from "./client"
|
||||
import { findServerForExtension } from "./config"
|
||||
import { SYMBOL_KIND_MAP, SEVERITY_MAP } from "./constants"
|
||||
import { SEVERITY_MAP } from "./constants"
|
||||
import type {
|
||||
HoverResult,
|
||||
DocumentSymbol,
|
||||
SymbolInfo,
|
||||
Location,
|
||||
LocationLink,
|
||||
Diagnostic,
|
||||
PrepareRenameResult,
|
||||
PrepareRenameDefaultBehavior,
|
||||
Range,
|
||||
WorkspaceEdit,
|
||||
TextEdit,
|
||||
CodeAction,
|
||||
Command,
|
||||
ServerLookupResult,
|
||||
} from "./types"
|
||||
|
||||
@@ -113,73 +106,11 @@ export async function withLspClient<T>(filePath: string, fn: (client: LSPClient)
|
||||
}
|
||||
}
|
||||
|
||||
export function formatHoverResult(result: HoverResult | null): string {
|
||||
if (!result) return "No hover information available"
|
||||
|
||||
const contents = result.contents
|
||||
if (typeof contents === "string") {
|
||||
return contents
|
||||
}
|
||||
|
||||
if (Array.isArray(contents)) {
|
||||
return contents
|
||||
.map((c) => (typeof c === "string" ? c : c.value))
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
}
|
||||
|
||||
if (typeof contents === "object" && "value" in contents) {
|
||||
return contents.value
|
||||
}
|
||||
|
||||
return "No hover information available"
|
||||
}
|
||||
|
||||
export function formatLocation(loc: Location | LocationLink): string {
|
||||
if ("targetUri" in loc) {
|
||||
const uri = uriToPath(loc.targetUri)
|
||||
const line = loc.targetRange.start.line + 1
|
||||
const char = loc.targetRange.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
const uri = uriToPath(loc.uri)
|
||||
const line = loc.range.start.line + 1
|
||||
const char = loc.range.start.character
|
||||
return `${uri}:${line}:${char}`
|
||||
}
|
||||
|
||||
export function formatSymbolKind(kind: number): string {
|
||||
return SYMBOL_KIND_MAP[kind] || `Unknown(${kind})`
|
||||
}
|
||||
|
||||
export function formatSeverity(severity: number | undefined): string {
|
||||
if (!severity) return "unknown"
|
||||
return SEVERITY_MAP[severity] || `unknown(${severity})`
|
||||
}
|
||||
|
||||
export function formatDocumentSymbol(symbol: DocumentSymbol, indent = 0): string {
|
||||
const prefix = " ".repeat(indent)
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const line = symbol.range.start.line + 1
|
||||
let result = `${prefix}${symbol.name} (${kind}) - line ${line}`
|
||||
|
||||
if (symbol.children && symbol.children.length > 0) {
|
||||
for (const child of symbol.children) {
|
||||
result += "\n" + formatDocumentSymbol(child, indent + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatSymbolInfo(symbol: SymbolInfo): string {
|
||||
const kind = formatSymbolKind(symbol.kind)
|
||||
const loc = formatLocation(symbol.location)
|
||||
const container = symbol.containerName ? ` (in ${symbol.containerName})` : ""
|
||||
return `${symbol.name} (${kind})${container} - ${loc}`
|
||||
}
|
||||
|
||||
export function formatDiagnostic(diag: Diagnostic): string {
|
||||
const severity = formatSeverity(diag.severity)
|
||||
const line = diag.range.start.line + 1
|
||||
@@ -292,38 +223,6 @@ export function formatWorkspaceEdit(edit: WorkspaceEdit | null): string {
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
export function formatCodeAction(action: CodeAction): string {
|
||||
let result = `[${action.kind || "action"}] ${action.title}`
|
||||
|
||||
if (action.isPreferred) {
|
||||
result += " ⭐"
|
||||
}
|
||||
|
||||
if (action.disabled) {
|
||||
result += ` (disabled: ${action.disabled.reason})`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatCodeActions(actions: (CodeAction | Command)[] | null): string {
|
||||
if (!actions || actions.length === 0) return "No code actions available"
|
||||
|
||||
const lines: string[] = []
|
||||
|
||||
for (let i = 0; i < actions.length; i++) {
|
||||
const action = actions[i]
|
||||
|
||||
if ("command" in action && typeof action.command === "string" && !("kind" in action)) {
|
||||
lines.push(`${i + 1}. [command] ${(action as Command).title}`)
|
||||
} else {
|
||||
lines.push(`${i + 1}. ${formatCodeAction(action as CodeAction)}`)
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
export interface ApplyResult {
|
||||
success: boolean
|
||||
filesModified: string[]
|
||||
|
||||
Reference in New Issue
Block a user