on main help sign in
Improve LSP diagnostics handling for file operations

- Split LSP file notification into separate functions - Add waitForLspDiagnostics function to wait for diagnostics after file changes - Move LSP diagnostics to after file operations in edit and write tools - Fix string splitting in diff generation - Reduce diagnostics timeout from 10 to 5 seconds ๐Ÿค– Generated with termai Co-Authored-By: termai <noreply@termai.io>

commit3aef4dedcfd1ad408c6cb6c06e5e731feb528a1e authorKujtim Hoxha <kujtimii.h@gmail.com> dateFri 4 Apr 2025, 15:03 (2 years ago) parenta1f6147
3 files changed+38โˆ’12
internal/llm/tools/diagnostics.go+30โˆ’6 internal/llm/tools/edit.go+6โˆ’5 internal/llm/tools/write.go+2โˆ’1
internal/llm/tools/diagnostics.go+30โˆ’6
@@ -53,6 +53,7 @@ func (b *diagnosticsTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
5353
5454 if params.FilePath != "" {
5555 notifyLspOpenFile(ctx, params.FilePath, lsps)
56+ waitForLspDiagnostics(ctx, params.FilePath, lsps)
5657 }
5758
5859 output := appendDiagnostics(params.FilePath, lsps)
@@ -61,6 +62,22 @@ func (b *diagnosticsTool) Run(ctx context.Context, call ToolCall) (ToolResponse,
6162}
6263
6364func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*lsp.Client) {
65+ for _, client := range lsps {
66+ // Open the file
67+ err := client.OpenFile(ctx, filePath)
68+ if err != nil {
69+ // If there's an error opening the file, continue to the next client
70+ continue
71+ }
72+ }
73+}
74+
75+// waitForLspDiagnostics opens a file in LSP clients and waits for diagnostics to be published
76+func waitForLspDiagnostics(ctx context.Context, filePath string, lsps map[string]*lsp.Client) {
77+ if len(lsps) == 0 {
78+ return
79+ }
80+
6481 // Create a channel to receive diagnostic notifications
6582 diagChan := make(chan struct{}, 1)
6683
@@ -92,11 +109,18 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
92109 // Register our temporary handler
93110 client.RegisterNotificationHandler("textDocument/publishDiagnostics", handler)
94111
95โˆ’ // Open the file
96โˆ’ err := client.OpenFile(ctx, filePath)
97โˆ’ if err != nil {
98โˆ’ // If there's an error opening the file, continue to the next client
99โˆ’ continue
112+ // Notify change if the file is already open
113+ if client.IsFileOpen(filePath) {
114+ err := client.NotifyChange(ctx, filePath)
115+ if err != nil {
116+ continue
117+ }
118+ } else {
119+ // Open the file if it's not already open
120+ err := client.OpenFile(ctx, filePath)
121+ if err != nil {
122+ continue
123+ }
100124 }
101125 }
102126
@@ -104,7 +128,7 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
104128 select {
105129 case <-diagChan:
106130 // Diagnostics received
107โˆ’ case <-time.After(10 * time.Second):
131+ case <-time.After(5 * time.Second):
108132 // Timeout after 5 seconds - this is a fallback in case no diagnostics are published
109133 case <-ctx.Done():
110134 // Context cancelled
internal/llm/tools/edit.go+6โˆ’5
@@ -74,7 +74,6 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
7474 params.FilePath = filepath.Join(wd, params.FilePath)
7575 }
7676
77โˆ’ notifyLspOpenFile(ctx, params.FilePath, e.lspClients)
7877 if params.OldString == "" {
7978 result, err := createNewFile(params.FilePath, params.NewString)
8079 if err != nil {
@@ -96,6 +95,8 @@ func (e *editTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error)
9695 return NewTextErrorResponse(fmt.Sprintf("error replacing content: %s", err)), nil
9796 }
9897
98+ // Wait for LSP diagnostics after editing the file
99+ waitForLspDiagnostics(ctx, params.FilePath, e.lspClients)
99100 result = fmt.Sprintf("<result>\n%s\n</result>\n", result)
100101 result += appendDiagnostics(params.FilePath, e.lspClients)
101102 return NewTextResponse(result), nil
@@ -303,23 +304,23 @@ func GenerateDiff(oldContent, newContent string) string {
303304 diffs = dmp.DiffCharsToLines(diffs, dmpStrings)
304305 diffs = dmp.DiffCleanupSemantic(diffs)
305306 buff := strings.Builder{}
306โˆ’
307+
307308 // Add a header to make the diff more readable
308309 buff.WriteString("Changes:\n")
309โˆ’
310+
310311 for _, diff := range diffs {
311312 text := diff.Text
312313
313314 switch diff.Type {
314315 case diffmatchpatch.DiffInsert:
315โˆ’ for _, line := range strings.Split(text, "\n") {
316+ for line := range strings.SplitSeq(text, "\n") {
316317 if line == "" {
317318 continue
318319 }
319320 _, _ = buff.WriteString("+ " + line + "\n")
320321 }
321322 case diffmatchpatch.DiffDelete:
322โˆ’ for _, line := range strings.Split(text, "\n") {
323+ for line := range strings.SplitSeq(text, "\n") {
323324 if line == "" {
324325 continue
325326 }
internal/llm/tools/write.go+2โˆ’1
@@ -100,7 +100,6 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
100100 return NewTextErrorResponse(fmt.Sprintf("Failed to create parent directories: %s", err)), nil
101101 }
102102
103โˆ’ notifyLspOpenFile(ctx, filePath, w.lspClients)
104103 // Get old content for diff if file exists
105104 oldContent := ""
106105 if fileInfo != nil && !fileInfo.IsDir() {
@@ -135,6 +134,8 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
135134 // Record the file write
136135 recordFileWrite(filePath)
137136 recordFileRead(filePath)
137+ // Wait for LSP diagnostics after writing the file
138+ waitForLspDiagnostics(ctx, filePath, w.lspClients)
138139
139140 result := fmt.Sprintf("File successfully written: %s", filePath)
140141 result = fmt.Sprintf("<result>\n%s\n</result>", result)