lsp.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import z from "zod"
  2. import { Tool } from "./tool"
  3. import path from "path"
  4. import { LSP } from "../lsp"
  5. import DESCRIPTION from "./lsp.txt"
  6. import { Instance } from "../project/instance"
  7. import { pathToFileURL } from "url"
  8. const operations = [
  9. "goToDefinition",
  10. "findReferences",
  11. "hover",
  12. "documentSymbol",
  13. "workspaceSymbol",
  14. "goToImplementation",
  15. "prepareCallHierarchy",
  16. "incomingCalls",
  17. "outgoingCalls",
  18. ] as const
  19. export const LspTool = Tool.define("lsp", {
  20. description: DESCRIPTION,
  21. parameters: z.object({
  22. operation: z.enum(operations).describe("The LSP operation to perform"),
  23. filePath: z.string().describe("The absolute or relative path to the file"),
  24. line: z.number().int().min(1).describe("The line number (1-based, as shown in editors)"),
  25. character: z.number().int().min(1).describe("The character offset (1-based, as shown in editors)"),
  26. }),
  27. execute: async (args, ctx) => {
  28. await ctx.ask({
  29. permission: "lsp",
  30. patterns: ["*"],
  31. always: ["*"],
  32. metadata: {},
  33. })
  34. const file = path.isAbsolute(args.filePath) ? args.filePath : path.join(Instance.directory, args.filePath)
  35. const uri = pathToFileURL(file).href
  36. const position = {
  37. file,
  38. line: args.line - 1,
  39. character: args.character - 1,
  40. }
  41. const relPath = path.relative(Instance.worktree, file)
  42. const title = `${args.operation} ${relPath}:${args.line}:${args.character}`
  43. const exists = await Bun.file(file).exists()
  44. if (!exists) {
  45. throw new Error(`File not found: ${file}`)
  46. }
  47. const available = await LSP.hasClients(file)
  48. if (!available) {
  49. throw new Error("No LSP server available for this file type.")
  50. }
  51. await LSP.touchFile(file, true)
  52. const result: unknown[] = await (async () => {
  53. switch (args.operation) {
  54. case "goToDefinition":
  55. return LSP.definition(position)
  56. case "findReferences":
  57. return LSP.references(position)
  58. case "hover":
  59. return LSP.hover(position)
  60. case "documentSymbol":
  61. return LSP.documentSymbol(uri)
  62. case "workspaceSymbol":
  63. return LSP.workspaceSymbol("")
  64. case "goToImplementation":
  65. return LSP.implementation(position)
  66. case "prepareCallHierarchy":
  67. return LSP.prepareCallHierarchy(position)
  68. case "incomingCalls":
  69. return LSP.incomingCalls(position)
  70. case "outgoingCalls":
  71. return LSP.outgoingCalls(position)
  72. }
  73. })()
  74. const output = (() => {
  75. if (result.length === 0) return `No results found for ${args.operation}`
  76. return JSON.stringify(result, null, 2)
  77. })()
  78. return {
  79. title,
  80. metadata: { result },
  81. output,
  82. }
  83. },
  84. })