11import z from "zod"
2+ import { Effect } from "effect"
23import { Tool } from "./tool"
34import path from "path"
45import { LSP } from "../lsp"
56import DESCRIPTION from "./lsp.txt"
67import { Instance } from "../project/instance"
78import { pathToFileURL } from "url"
8- import { assertExternalDirectory } from "./external-directory"
9- import { Filesystem } from "../util /filesystem"
9+ import { assertExternalDirectoryEffect } from "./external-directory"
10+ import { AppFileSystem } from "../filesystem"
1011
1112const operations = [
1213 "goToDefinition" ,
@@ -20,78 +21,70 @@ const operations = [
2021 "outgoingCalls" ,
2122] as const
2223
23- export const LspTool = Tool . define ( "lsp" , {
24- description : DESCRIPTION ,
25- parameters : z . object ( {
26- operation : z . enum ( operations ) . describe ( "The LSP operation to perform" ) ,
27- filePath : z . string ( ) . describe ( "The absolute or relative path to the file" ) ,
28- line : z . number ( ) . int ( ) . min ( 1 ) . describe ( "The line number (1-based, as shown in editors)" ) ,
29- character : z . number ( ) . int ( ) . min ( 1 ) . describe ( "The character offset (1-based, as shown in editors)" ) ,
30- } ) ,
31- execute : async ( args , ctx ) => {
32- const file = path . isAbsolute ( args . filePath ) ? args . filePath : path . join ( Instance . directory , args . filePath )
33- await assertExternalDirectory ( ctx , file )
34-
35- await ctx . ask ( {
36- permission : "lsp" ,
37- patterns : [ "*" ] ,
38- always : [ "*" ] ,
39- metadata : { } ,
40- } )
41- const uri = pathToFileURL ( file ) . href
42- const position = {
43- file,
44- line : args . line - 1 ,
45- character : args . character - 1 ,
46- }
24+ export const LspTool = Tool . defineEffect (
25+ "lsp" ,
26+ Effect . gen ( function * ( ) {
27+ const lsp = yield * LSP . Service
28+ const fs = yield * AppFileSystem . Service
4729
48- const relPath = path . relative ( Instance . worktree , file )
49- const title = `${ args . operation } ${ relPath } :${ args . line } :${ args . character } `
30+ return {
31+ description : DESCRIPTION ,
32+ parameters : z . object ( {
33+ operation : z . enum ( operations ) . describe ( "The LSP operation to perform" ) ,
34+ filePath : z . string ( ) . describe ( "The absolute or relative path to the file" ) ,
35+ line : z . number ( ) . int ( ) . min ( 1 ) . describe ( "The line number (1-based, as shown in editors)" ) ,
36+ character : z . number ( ) . int ( ) . min ( 1 ) . describe ( "The character offset (1-based, as shown in editors)" ) ,
37+ } ) ,
38+ execute : ( args : { operation : ( typeof operations ) [ number ] ; filePath : string ; line : number ; character : number } , ctx : Tool . Context ) =>
39+ Effect . gen ( function * ( ) {
40+ const file = path . isAbsolute ( args . filePath ) ? args . filePath : path . join ( Instance . directory , args . filePath )
41+ yield * assertExternalDirectoryEffect ( ctx , file )
42+ yield * Effect . promise ( ( ) =>
43+ ctx . ask ( { permission : "lsp" , patterns : [ "*" ] , always : [ "*" ] , metadata : { } } ) ,
44+ )
5045
51- const exists = await Filesystem . exists ( file )
52- if ( ! exists ) {
53- throw new Error ( `File not found: ${ file } ` )
54- }
46+ const uri = pathToFileURL ( file ) . href
47+ const position = { file , line : args . line - 1 , character : args . character - 1 }
48+ const relPath = path . relative ( Instance . worktree , file )
49+ const title = ` ${ args . operation } ${ relPath } : ${ args . line } : ${ args . character } `
5550
56- const available = await LSP . hasClients ( file )
57- if ( ! available ) {
58- throw new Error ( "No LSP server available for this file type." )
59- }
51+ const exists = yield * fs . existsSafe ( file )
52+ if ( ! exists ) throw new Error ( `File not found: ${ file } ` )
6053
61- await LSP . touchFile ( file , true )
54+ const available = yield * lsp . hasClients ( file )
55+ if ( ! available ) throw new Error ( "No LSP server available for this file type." )
6256
63- const result : unknown [ ] = await ( async ( ) => {
64- switch ( args . operation ) {
65- case "goToDefinition" :
66- return LSP . definition ( position )
67- case "findReferences" :
68- return LSP . references ( position )
69- case "hover" :
70- return LSP . hover ( position )
71- case "documentSymbol" :
72- return LSP . documentSymbol ( uri )
73- case "workspaceSymbol" :
74- return LSP . workspaceSymbol ( "" )
75- case "goToImplementation" :
76- return LSP . implementation ( position )
77- case "prepareCallHierarchy" :
78- return LSP . prepareCallHierarchy ( position )
79- case "incomingCalls" :
80- return LSP . incomingCalls ( position )
81- case "outgoingCalls" :
82- return LSP . outgoingCalls ( position )
83- }
84- } ) ( )
57+ yield * lsp . touchFile ( file , true )
8558
86- const output = ( ( ) => {
87- if ( result . length === 0 ) return `No results found for ${ args . operation } `
88- return JSON . stringify ( result , null , 2 )
89- } ) ( )
59+ const result : unknown [ ] = yield * ( ( ) => {
60+ switch ( args . operation ) {
61+ case "goToDefinition" :
62+ return lsp . definition ( position )
63+ case "findReferences" :
64+ return lsp . references ( position )
65+ case "hover" :
66+ return lsp . hover ( position )
67+ case "documentSymbol" :
68+ return lsp . documentSymbol ( uri )
69+ case "workspaceSymbol" :
70+ return lsp . workspaceSymbol ( "" )
71+ case "goToImplementation" :
72+ return lsp . implementation ( position )
73+ case "prepareCallHierarchy" :
74+ return lsp . prepareCallHierarchy ( position )
75+ case "incomingCalls" :
76+ return lsp . incomingCalls ( position )
77+ case "outgoingCalls" :
78+ return lsp . outgoingCalls ( position )
79+ }
80+ } ) ( )
9081
91- return {
92- title,
93- metadata : { result } ,
94- output,
82+ return {
83+ title,
84+ metadata : { result } ,
85+ output : result . length === 0 ? `No results found for ${ args . operation } ` : JSON . stringify ( result , null , 2 ) ,
86+ }
87+ } ) . pipe ( Effect . runPromise ) ,
9588 }
96- } ,
97- } )
89+ } ) ,
90+ )
0 commit comments