mirror of
https://github.com/sst/opencode.git
synced 2025-08-13 09:49:28 +00:00
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>
This commit is contained in:
parent
6bb1c84f7f
commit
c185dc84d6
5 changed files with 87 additions and 22 deletions
|
@ -72,6 +72,7 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
|
||||||
|
|
||||||
// Create a notification handler that will signal when diagnostics are received
|
// Create a notification handler that will signal when diagnostics are received
|
||||||
handler := func(params json.RawMessage) {
|
handler := func(params json.RawMessage) {
|
||||||
|
lsp.HandleDiagnostics(client, params)
|
||||||
var diagParams protocol.PublishDiagnosticsParams
|
var diagParams protocol.PublishDiagnosticsParams
|
||||||
if err := json.Unmarshal(params, &diagParams); err != nil {
|
if err := json.Unmarshal(params, &diagParams); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -103,8 +104,8 @@ func notifyLspOpenFile(ctx context.Context, filePath string, lsps map[string]*ls
|
||||||
select {
|
select {
|
||||||
case <-diagChan:
|
case <-diagChan:
|
||||||
// Diagnostics received
|
// Diagnostics received
|
||||||
case <-time.After(5 * time.Second):
|
case <-time.After(10 * time.Second):
|
||||||
// Timeout after 2 seconds - this is a fallback in case no diagnostics are published
|
// Timeout after 5 seconds - this is a fallback in case no diagnostics are published
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// Context cancelled
|
// Context cancelled
|
||||||
}
|
}
|
||||||
|
|
|
@ -303,23 +303,46 @@ func GenerateDiff(oldContent, newContent string) string {
|
||||||
diffs = dmp.DiffCharsToLines(diffs, dmpStrings)
|
diffs = dmp.DiffCharsToLines(diffs, dmpStrings)
|
||||||
diffs = dmp.DiffCleanupSemantic(diffs)
|
diffs = dmp.DiffCleanupSemantic(diffs)
|
||||||
buff := strings.Builder{}
|
buff := strings.Builder{}
|
||||||
|
|
||||||
|
// Add a header to make the diff more readable
|
||||||
|
buff.WriteString("Changes:\n")
|
||||||
|
|
||||||
for _, diff := range diffs {
|
for _, diff := range diffs {
|
||||||
text := diff.Text
|
text := diff.Text
|
||||||
|
|
||||||
switch diff.Type {
|
switch diff.Type {
|
||||||
case diffmatchpatch.DiffInsert:
|
case diffmatchpatch.DiffInsert:
|
||||||
for line := range strings.SplitSeq(text, "\n") {
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_, _ = buff.WriteString("+ " + line + "\n")
|
_, _ = buff.WriteString("+ " + line + "\n")
|
||||||
}
|
}
|
||||||
case diffmatchpatch.DiffDelete:
|
case diffmatchpatch.DiffDelete:
|
||||||
for line := range strings.SplitSeq(text, "\n") {
|
for _, line := range strings.Split(text, "\n") {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_, _ = buff.WriteString("- " + line + "\n")
|
_, _ = buff.WriteString("- " + line + "\n")
|
||||||
}
|
}
|
||||||
case diffmatchpatch.DiffEqual:
|
case diffmatchpatch.DiffEqual:
|
||||||
if len(text) > 40 {
|
// Only show a small context for unchanged text
|
||||||
_, _ = buff.WriteString(" " + text[:20] + "..." + text[len(text)-20:] + "\n")
|
lines := strings.Split(text, "\n")
|
||||||
|
if len(lines) > 3 {
|
||||||
|
// Show only first and last line of context with a separator
|
||||||
|
if lines[0] != "" {
|
||||||
|
_, _ = buff.WriteString(" " + lines[0] + "\n")
|
||||||
|
}
|
||||||
|
_, _ = buff.WriteString(" ...\n")
|
||||||
|
if lines[len(lines)-1] != "" {
|
||||||
|
_, _ = buff.WriteString(" " + lines[len(lines)-1] + "\n")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for line := range strings.SplitSeq(text, "\n") {
|
// Show all lines for small contexts
|
||||||
|
for _, line := range lines {
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_, _ = buff.WriteString(" " + line + "\n")
|
_, _ = buff.WriteString(" " + line + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,15 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyLspOpenFile(ctx, filePath, w.lspClients)
|
notifyLspOpenFile(ctx, filePath, w.lspClients)
|
||||||
|
// Get old content for diff if file exists
|
||||||
|
oldContent := ""
|
||||||
|
if fileInfo != nil && !fileInfo.IsDir() {
|
||||||
|
oldBytes, readErr := os.ReadFile(filePath)
|
||||||
|
if readErr == nil {
|
||||||
|
oldContent = string(oldBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p := permission.Default.Request(
|
p := permission.Default.Request(
|
||||||
permission.CreatePermissionRequest{
|
permission.CreatePermissionRequest{
|
||||||
Path: filePath,
|
Path: filePath,
|
||||||
|
@ -109,7 +118,7 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
|
||||||
Description: fmt.Sprintf("Create file %s", filePath),
|
Description: fmt.Sprintf("Create file %s", filePath),
|
||||||
Params: WritePermissionsParams{
|
Params: WritePermissionsParams{
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
Content: GenerateDiff("", params.Content),
|
Content: GenerateDiff(oldContent, params.Content),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -79,10 +79,18 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
p.isViewportFocus = !p.isViewportFocus
|
p.isViewportFocus = !p.isViewportFocus
|
||||||
if p.isViewportFocus {
|
if p.isViewportFocus {
|
||||||
p.selectOption.Blur()
|
p.selectOption.Blur()
|
||||||
|
// Add a visual indicator for focus change
|
||||||
|
cmds = append(cmds, tea.Batch(
|
||||||
|
util.CmdHandler(util.InfoMsg("Viewing content - use arrow keys to scroll")),
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
p.selectOption.Focus()
|
p.selectOption.Focus()
|
||||||
|
// Add a visual indicator for focus change
|
||||||
|
cmds = append(cmds, tea.Batch(
|
||||||
|
util.CmdHandler(util.InfoMsg("Select an action")),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,34 +141,55 @@ func (p *permissionDialogCmp) render() string {
|
||||||
case tools.BashToolName:
|
case tools.BashToolName:
|
||||||
pr := p.permission.Params.(tools.BashPermissionsParams)
|
pr := p.permission.Params.(tools.BashPermissionsParams)
|
||||||
headerParts = append(headerParts, keyStyle.Render("Command:"))
|
headerParts = append(headerParts, keyStyle.Render("Command:"))
|
||||||
content, _ = r.Render(fmt.Sprintf("```bash\n%s\n```", pr.Command))
|
content = fmt.Sprintf("```bash\n%s\n```", pr.Command)
|
||||||
case tools.EditToolName:
|
case tools.EditToolName:
|
||||||
pr := p.permission.Params.(tools.EditPermissionsParams)
|
pr := p.permission.Params.(tools.EditPermissionsParams)
|
||||||
headerParts = append(headerParts, keyStyle.Render("Update"))
|
headerParts = append(headerParts, keyStyle.Render("Update"))
|
||||||
content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Diff))
|
content = fmt.Sprintf("```\n%s\n```", pr.Diff)
|
||||||
case tools.WriteToolName:
|
case tools.WriteToolName:
|
||||||
pr := p.permission.Params.(tools.WritePermissionsParams)
|
pr := p.permission.Params.(tools.WritePermissionsParams)
|
||||||
headerParts = append(headerParts, keyStyle.Render("Content"))
|
headerParts = append(headerParts, keyStyle.Render("Content"))
|
||||||
content, _ = r.Render(fmt.Sprintf("```diff\n%s\n```", pr.Content))
|
content = fmt.Sprintf("```\n%s\n```", pr.Content)
|
||||||
case tools.FetchToolName:
|
case tools.FetchToolName:
|
||||||
pr := p.permission.Params.(tools.FetchPermissionsParams)
|
pr := p.permission.Params.(tools.FetchPermissionsParams)
|
||||||
headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
|
headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
|
||||||
default:
|
default:
|
||||||
content, _ = r.Render(p.permission.Description)
|
content = p.permission.Description
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderedContent, _ := r.Render(content)
|
||||||
headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
|
headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
|
||||||
p.contentViewPort.Width = p.width - 2 - 2
|
p.contentViewPort.Width = p.width - 2 - 2
|
||||||
p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
|
||||||
p.contentViewPort.SetContent(content)
|
p.contentViewPort.SetContent(renderedContent)
|
||||||
contentBorder := lipgloss.RoundedBorder()
|
|
||||||
|
// Make focus change more apparent with different border styles and colors
|
||||||
|
var contentBorder lipgloss.Border
|
||||||
|
var borderColor lipgloss.TerminalColor
|
||||||
|
|
||||||
if p.isViewportFocus {
|
if p.isViewportFocus {
|
||||||
contentBorder = lipgloss.DoubleBorder()
|
contentBorder = lipgloss.DoubleBorder()
|
||||||
|
borderColor = styles.Blue
|
||||||
|
} else {
|
||||||
|
contentBorder = lipgloss.RoundedBorder()
|
||||||
|
borderColor = styles.Flamingo
|
||||||
}
|
}
|
||||||
cotentStyle := lipgloss.NewStyle().MarginTop(1).Padding(0, 1).Border(contentBorder).BorderForeground(styles.Flamingo)
|
|
||||||
contentFinal := cotentStyle.Render(p.contentViewPort.View())
|
contentStyle := lipgloss.NewStyle().
|
||||||
if content == "" {
|
MarginTop(1).
|
||||||
|
Padding(0, 1).
|
||||||
|
Border(contentBorder).
|
||||||
|
BorderForeground(borderColor)
|
||||||
|
|
||||||
|
if p.isViewportFocus {
|
||||||
|
contentStyle = contentStyle.BorderBackground(styles.Surface0)
|
||||||
|
}
|
||||||
|
|
||||||
|
contentFinal := contentStyle.Render(p.contentViewPort.View())
|
||||||
|
if renderedContent == "" {
|
||||||
contentFinal = ""
|
contentFinal = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return lipgloss.JoinVertical(
|
return lipgloss.JoinVertical(
|
||||||
lipgloss.Top,
|
lipgloss.Top,
|
||||||
headerContent,
|
headerContent,
|
||||||
|
@ -241,12 +270,13 @@ func NewPermissionDialogCmd(permission permission.PermissionRequest) tea.Cmd {
|
||||||
minWidth := 100
|
minWidth := 100
|
||||||
minHeight := 30
|
minHeight := 30
|
||||||
|
|
||||||
|
// Make the dialog size more appropriate for bash commands
|
||||||
switch permission.ToolName {
|
switch permission.ToolName {
|
||||||
case tools.BashToolName:
|
case tools.BashToolName:
|
||||||
widthRatio = 0.5
|
widthRatio = 0.7
|
||||||
heightRatio = 0.3
|
heightRatio = 0.5
|
||||||
minWidth = 80
|
minWidth = 100
|
||||||
minHeight = 20
|
minHeight = 30
|
||||||
}
|
}
|
||||||
// Return the dialog command
|
// Return the dialog command
|
||||||
return util.CmdHandler(core.DialogMsg{
|
return util.CmdHandler(core.DialogMsg{
|
||||||
|
|
|
@ -166,6 +166,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
a.dialog = d.(core.DialogCmp)
|
a.dialog = d.(core.DialogCmp)
|
||||||
return a, cmd
|
return a, cmd
|
||||||
}
|
}
|
||||||
|
s, _ := a.status.Update(msg)
|
||||||
|
a.status = s
|
||||||
p, cmd := a.pages[a.currentPage].Update(msg)
|
p, cmd := a.pages[a.currentPage].Update(msg)
|
||||||
a.pages[a.currentPage] = p
|
a.pages[a.currentPage] = p
|
||||||
return a, cmd
|
return a, cmd
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue