Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit c185dc8

Browse files
kujtimiihoxhatermai
andcommitted
Enhance UI feedback and improve file diff visualization
- Improve diff display in permission dialogs with better formatting - Add visual indicators for focus changes in permission dialogs - Increase diagnostics timeout from 5 to 10 seconds - Fix write tool to show proper diffs for existing files - Update status component to properly handle messages 🤖 Generated with termai Co-Authored-By: termai <noreply@termai.io>
1 parent 6bb1c84 commit c185dc8

File tree

5 files changed

+87
-22
lines changed

5 files changed

+87
-22
lines changed

internal/llm/tools/diagnostics.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
7272

7373
// Create a notification handler that will signal when diagnostics are received
7474
handler := func(params json.RawMessage) {
75+
lsp.HandleDiagnostics(client, params)
7576
var diagParams protocol.PublishDiagnosticsParams
7677
if err := json.Unmarshal(params, &diagParams); err != nil {
7778
return
@@ -103,8 +104,8 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
103104
select {
104105
case <-diagChan:
105106
// Diagnostics received
106-
case <-time.After(5 * time.Second):
107-
// Timeout after 2 seconds - this is a fallback in case no diagnostics are published
107+
case <-time.After(10 * time.Second):
108+
// Timeout after 5 seconds - this is a fallback in case no diagnostics are published
108109
case <-ctx.Done():
109110
// Context cancelled
110111
}

internal/llm/tools/edit.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -303,23 +303,46 @@ func GenerateDiff(oldContent, newContent string) string {
303303
diffs = dmp.DiffCharsToLines(diffs, dmpStrings)
304304
diffs = dmp.DiffCleanupSemantic(diffs)
305305
buff := strings.Builder{}
306+
307+
// Add a header to make the diff more readable
308+
buff.WriteString("Changes:\n")
309+
306310
for _, diff := range diffs {
307311
text := diff.Text
308312

309313
switch diff.Type {
310314
case diffmatchpatch.DiffInsert:
311-
for line := range strings.SplitSeq(text, "\n") {
315+
for _, line := range strings.Split(text, "\n") {
316+
if line == "" {
317+
continue
318+
}
312319
_, _ = buff.WriteString("+ " + line + "\n")
313320
}
314321
case diffmatchpatch.DiffDelete:
315-
for line := range strings.SplitSeq(text, "\n") {
322+
for _, line := range strings.Split(text, "\n") {
323+
if line == "" {
324+
continue
325+
}
316326
_, _ = buff.WriteString("- " + line + "\n")
317327
}
318328
case diffmatchpatch.DiffEqual:
319-
if len(text) > 40 {
320-
_, _ = buff.WriteString(" " + text[:20] + "..." + text[len(text)-20:] + "\n")
329+
// Only show a small context for unchanged text
330+
lines := strings.Split(text, "\n")
331+
if len(lines) > 3 {
332+
// Show only first and last line of context with a separator
333+
if lines[0] != "" {
334+
_, _ = buff.WriteString(" " + lines[0] + "\n")
335+
}
336+
_, _ = buff.WriteString(" ...\n")
337+
if lines[len(lines)-1] != "" {
338+
_, _ = buff.WriteString(" " + lines[len(lines)-1] + "\n")
339+
}
321340
} else {
322-
for line := range strings.SplitSeq(text, "\n") {
341+
// Show all lines for small contexts
342+
for _, line := range lines {
343+
if line == "" {
344+
continue
345+
}
323346
_, _ = buff.WriteString(" " + line + "\n")
324347
}
325348
}

internal/llm/tools/write.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
101101
}
102102

103103
notifyLspOpenFile(ctx, filePath, w.lspClients)
104+
// Get old content for diff if file exists
105+
oldContent := ""
106+
if fileInfo != nil && !fileInfo.IsDir() {
107+
oldBytes, readErr := os.ReadFile(filePath)
108+
if readErr == nil {
109+
oldContent = string(oldBytes)
110+
}
111+
}
112+
104113
p := permission.Default.Request(
105114
permission.CreatePermissionRequest{
106115
Path: filePath,
@@ -109,7 +118,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
109118
Description: fmt.Sprintf("Create file %s", filePath),
110119
Params: WritePermissionsParams{
111120
FilePath: filePath,
112-
Content: GenerateDiff("", params.Content),
121+
Content: GenerateDiff(oldContent, params.Content),
113122
},
114123
},
115124
)

internal/tui/components/dialog/permission.go

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,18 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
7979
p.isViewportFocus = !p.isViewportFocus
8080
if p.isViewportFocus {
8181
p.selectOption.Blur()
82+
// Add a visual indicator for focus change
83+
cmds = append(cmds, tea.Batch(
84+
util.CmdHandler(util.InfoMsg("Viewing content - use arrow keys to scroll")),
85+
))
8286
} else {
8387
p.selectOption.Focus()
88+
// Add a visual indicator for focus change
89+
cmds = append(cmds, tea.Batch(
90+
util.CmdHandler(util.InfoMsg("Select an action")),
91+
))
8492
}
85-
return p, nil
93+
return p, tea.Batch(cmds...)
8694
}
8795
}
8896

@@ -133,34 +141,55 @@ func (p *permissionDialogCmp) render() string {
133141
case tools.BashToolName:
134142
pr := p.permission.Params.(tools.BashPermissionsParams)
135143
headerParts = append(headerParts, keyStyle.Render("Command:"))
136-
content, _ = r.Render(fmt.Sprintf("```bash\n%s\n```", pr.Command))
144+
content = fmt.Sprintf("```bash\n%s\n```", pr.Command)
137145
case tools.EditToolName:
138146
pr := p.permission.Params.(tools.EditPermissionsParams)
139147
headerParts = append(headerParts, keyStyle.Render("Update"))
140-
content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Diff))
148+
content = fmt.Sprintf("```\n%s\n```", pr.Diff)
141149
case tools.WriteToolName:
142150
pr := p.permission.Params.(tools.WritePermissionsParams)
143151
headerParts = append(headerParts, keyStyle.Render("Content"))
144-
content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Content))
152+
content = fmt.Sprintf("```\n%s\n```", pr.Content)
145153
case tools.FetchToolName:
146154
pr := p.permission.Params.(tools.FetchPermissionsParams)
147155
headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
148156
default:
149-
content, _ = r.Render(p.permission.Description)
157+
content = p.permission.Description
150158
}
159+
160+
renderedContent, _ := r.Render(content)
151161
headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
152162
p.contentViewPort.Width = p.width - 2 - 2
153163
p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
154-
p.contentViewPort.SetContent(content)
155-
contentBorder := lipgloss.RoundedBorder()
164+
p.contentViewPort.SetContent(renderedContent)
165+
166+
// Make focus change more apparent with different border styles and colors
167+
var contentBorder lipgloss.Border
168+
var borderColor lipgloss.TerminalColor
169+
156170
if p.isViewportFocus {
157171
contentBorder = lipgloss.DoubleBorder()
172+
borderColor = styles.Blue
173+
} else {
174+
contentBorder = lipgloss.RoundedBorder()
175+
borderColor = styles.Flamingo
176+
}
177+
178+
contentStyle := lipgloss.NewStyle().
179+
MarginTop(1).
180+
Padding(0, 1).
181+
Border(contentBorder).
182+
BorderForeground(borderColor)
183+
184+
if p.isViewportFocus {
185+
contentStyle = contentStyle.BorderBackground(styles.Surface0)
158186
}
159-
cotentStyle := lipgloss.NewStyle().MarginTop(1).Padding(0, 1).Border(contentBorder).BorderForeground(styles.Flamingo)
160-
contentFinal := cotentStyle.Render(p.contentViewPort.View())
161-
if content == "" {
187+
188+
contentFinal := contentStyle.Render(p.contentViewPort.View())
189+
if renderedContent == "" {
162190
contentFinal = ""
163191
}
192+
164193
return lipgloss.JoinVertical(
165194
lipgloss.Top,
166195
headerContent,
@@ -241,12 +270,13 @@ func NewPermissionDialogCmd(permission permission.PermissionRequest) tea.Cmd {
241270
minWidth := 100
242271
minHeight := 30
243272

273+
// Make the dialog size more appropriate for bash commands
244274
switch permission.ToolName {
245275
case tools.BashToolName:
246-
widthRatio = 0.5
247-
heightRatio = 0.3
248-
minWidth = 80
249-
minHeight = 20
276+
widthRatio = 0.7
277+
heightRatio = 0.5
278+
minWidth = 100
279+
minHeight = 30
250280
}
251281
// Return the dialog command
252282
return util.CmdHandler(core.DialogMsg{

internal/tui/tui.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
166166
a.dialog = d.(core.DialogCmp)
167167
return a, cmd
168168
}
169+
s, _ := a.status.Update(msg)
170+
a.status = s
169171
p, cmd := a.pages[a.currentPage].Update(msg)
170172
a.pages[a.currentPage] = p
171173
return a, cmd

0 commit comments

Comments
 (0)