on main help sign in
Improve diff display with colored formatting in permission dialogs
b28c1e0Kujtim Hoxha
internal/tui/components/dialog/permission.go+171−66
@@ -65,6 +65,45 @@ type permissionDialogCmp struct {
65 selectOption *huh.Select[string]
65 selectOption *huh.Select[string]
66}
66}
67
67
68+// formatDiff formats a diff string with colors for additions and deletions
69+func formatDiff(diffText string) string {
70+ lines := strings.Split(diffText, "\n")
71+ var formattedLines []string
72+
73+ // Define styles for different line types
74+ addStyle := lipgloss.NewStyle().Foreground(styles.Green)
75+ removeStyle := lipgloss.NewStyle().Foreground(styles.Red)
76+ headerStyle := lipgloss.NewStyle().Bold(true).Foreground(styles.Blue)
77+ contextStyle := lipgloss.NewStyle().Foreground(styles.SubText0)
78+
79+ // Process each line
80+ for _, line := range lines {
81+ if strings.HasPrefix(line, "+") {
82+ formattedLines = append(formattedLines, addStyle.Render(line))
83+ } else if strings.HasPrefix(line, "-") {
84+ formattedLines = append(formattedLines, removeStyle.Render(line))
85+ } else if strings.HasPrefix(line, "Changes:") || strings.HasPrefix(line, " ...") {
86+ formattedLines = append(formattedLines, headerStyle.Render(line))
87+ } else if strings.HasPrefix(line, " ") {
88+ formattedLines = append(formattedLines, contextStyle.Render(line))
89+ } else {
90+ formattedLines = append(formattedLines, line)
91+ }
92+ }
93+
94+ // Join all formatted lines
95+ content := strings.Join(formattedLines, "\n")
96+
97+ // Create a bordered box for the content
98+ contentStyle := lipgloss.NewStyle().
99+ MarginTop(1).
100+ Padding(0, 1).
101+ Border(lipgloss.RoundedBorder()).
102+ BorderForeground(styles.Flamingo)
103+
104+ return contentStyle.Render(content)
105+}
106+
68func (p *permissionDialogCmp) Init() tea.Cmd {
107func (p *permissionDialogCmp) Init() tea.Cmd {
69 return nil
108 return nil
70}
109}
@@ -132,93 +171,159 @@ func (p *permissionDialogCmp) render() string {
132 lipgloss.JoinHorizontal(lipgloss.Left, keyStyle.Render("Path:"), " ", valueStyle.Render(p.permission.Path)),
171 lipgloss.JoinHorizontal(lipgloss.Left, keyStyle.Render("Path:"), " ", valueStyle.Render(p.permission.Path)),
133 " ",
172 " ",
134 }
173 }
174+
175+ // Create the header content first so it can be used in all cases
176+ headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
177+
135 r, _ := glamour.NewTermRenderer(
178 r, _ := glamour.NewTermRenderer(
136 glamour.WithStyles(styles.CatppuccinMarkdownStyle()),
179 glamour.WithStyles(styles.CatppuccinMarkdownStyle()),
137 glamour.WithWordWrap(p.width-10),
180 glamour.WithWordWrap(p.width-10),
138 glamour.WithEmoji(),
181 glamour.WithEmoji(),
139 )
182 )
140− content := ""
183+
184+ // Handle different tool types
141 switch p.permission.ToolName {
185 switch p.permission.ToolName {
142 case tools.BashToolName:
186 case tools.BashToolName:
143 pr := p.permission.Params.(tools.BashPermissionsParams)
187 pr := p.permission.Params.(tools.BashPermissionsParams)
144 headerParts = append(headerParts, keyStyle.Render("Command:"))
188 headerParts = append(headerParts, keyStyle.Render("Command:"))
145− content = fmt.Sprintf("```bash\n%s\n```", pr.Command)
189+ content := fmt.Sprintf("```bash\n%s\n```", pr.Command)
190+
191+ renderedContent, _ := r.Render(content)
192+ p.contentViewPort.Width = p.width - 2 - 2
193+
194+ // Calculate content height dynamically based on content
195+ contentLines := len(strings.Split(renderedContent, "\n"))
196+ // Set a reasonable min/max for the viewport height
197+ minContentHeight := 3
198+ maxContentHeight := p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
199+
200+ // Add some padding to the content lines
201+ contentHeight := contentLines + 2
202+ if contentHeight < minContentHeight {
203+ contentHeight = minContentHeight
204+ }
205+ if contentHeight > maxContentHeight {
206+ contentHeight = maxContentHeight
207+ }
208+ p.contentViewPort.Height = contentHeight
209+
210+ p.contentViewPort.SetContent(renderedContent)
211+
212+ // Style the viewport
213+ var contentBorder lipgloss.Border
214+ var borderColor lipgloss.TerminalColor
215+
216+ if p.isViewportFocus {
217+ contentBorder = lipgloss.DoubleBorder()
218+ borderColor = styles.Blue
219+ } else {
220+ contentBorder = lipgloss.RoundedBorder()
221+ borderColor = styles.Flamingo
222+ }
223+
224+ contentStyle := lipgloss.NewStyle().
225+ MarginTop(1).
226+ Padding(0, 1).
227+ Border(contentBorder).
228+ BorderForeground(borderColor)
229+
230+ if p.isViewportFocus {
231+ contentStyle = contentStyle.BorderBackground(styles.Surface0)
232+ }
233+
234+ contentFinal := contentStyle.Render(p.contentViewPort.View())
235+
236+ return lipgloss.JoinVertical(
237+ lipgloss.Top,
238+ headerContent,
239+ contentFinal,
240+ form,
241+ )
242+
146 case tools.EditToolName:
243 case tools.EditToolName:
147 pr := p.permission.Params.(tools.EditPermissionsParams)
244 pr := p.permission.Params.(tools.EditPermissionsParams)
148 headerParts = append(headerParts, keyStyle.Render("Update"))
245 headerParts = append(headerParts, keyStyle.Render("Update"))
149− content = fmt.Sprintf("```\n%s\n```", pr.Diff)
246+ // Recreate header content with the updated headerParts
247+ headerContent = lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
248+ // Format the diff with colors instead of using markdown code block
249+ formattedDiff := formatDiff(pr.Diff)
250+ return lipgloss.JoinVertical(
251+ lipgloss.Top,
252+ headerContent,
253+ formattedDiff,
254+ form,
255+ )
256+
150 case tools.WriteToolName:
257 case tools.WriteToolName:
151 pr := p.permission.Params.(tools.WritePermissionsParams)
258 pr := p.permission.Params.(tools.WritePermissionsParams)
152 headerParts = append(headerParts, keyStyle.Render("Content"))
259 headerParts = append(headerParts, keyStyle.Render("Content"))
153− content = fmt.Sprintf("```\n%s\n```", pr.Content)
260+ // Recreate header content with the updated headerParts
261+ headerContent = lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
262+ // Format the diff with colors instead of using markdown code block
263+ formattedDiff := formatDiff(pr.Content)
264+ return lipgloss.JoinVertical(
265+ lipgloss.Top,
266+ headerContent,
267+ formattedDiff,
268+ form,
269+ )
270+
154 case tools.FetchToolName:
271 case tools.FetchToolName:
155 pr := p.permission.Params.(tools.FetchPermissionsParams)
272 pr := p.permission.Params.(tools.FetchPermissionsParams)
156 headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
273 headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
157− default:
158− content = p.permission.Description
274+ content := p.permission.Description
159− }
275+
160−
161− renderedContent, _ := r.Render(content)
276+ renderedContent, _ := r.Render(content)
162− headerContent := lipgloss.NewStyle().Padding(0, 1).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
163− p.contentViewPort.Width = p.width - 2 - 2
277+ p.contentViewPort.Width = p.width - 2 - 2
164−
165− // Calculate content height dynamically based on content
166− contentLines := len(strings.Split(renderedContent, "\n"))
167− // Set a reasonable min/max for the viewport height
168− minContentHeight := 3
169− maxContentHeight := p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
278+ p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
170−
279+ p.contentViewPort.SetContent(renderedContent)
171− // For bash commands, adjust height based on content length
280+
172− if p.permission.ToolName == tools.BashToolName {
281+ // Style the viewport
173− // Add some padding to the content lines
282+ contentStyle := lipgloss.NewStyle().
174− contentHeight := contentLines + 2
283+ MarginTop(1).
175− if contentHeight < minContentHeight {
284+ Padding(0, 1).
176− contentHeight = minContentHeight
285+ Border(lipgloss.RoundedBorder()).
286+ BorderForeground(styles.Flamingo)
287+
288+ contentFinal := contentStyle.Render(p.contentViewPort.View())
289+ if renderedContent == "" {
290+ contentFinal = ""
177 }
291 }
178− if contentHeight > maxContentHeight {
292+
179− contentHeight = maxContentHeight
293+ return lipgloss.JoinVertical(
294+ lipgloss.Top,
295+ headerContent,
296+ contentFinal,
297+ form,
298+ )
299+
300+ default:
301+ content := p.permission.Description
302+
303+ renderedContent, _ := r.Render(content)
304+ p.contentViewPort.Width = p.width - 2 - 2
305+ p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
306+ p.contentViewPort.SetContent(renderedContent)
307+
308+ // Style the viewport
309+ contentStyle := lipgloss.NewStyle().
310+ MarginTop(1).
311+ Padding(0, 1).
312+ Border(lipgloss.RoundedBorder()).
313+ BorderForeground(styles.Flamingo)
314+
315+ contentFinal := contentStyle.Render(p.contentViewPort.View())
316+ if renderedContent == "" {
317+ contentFinal = ""
180 }
318 }
181− p.contentViewPort.Height = contentHeight
319+
182− } else {
320+ return lipgloss.JoinVertical(
183− // For other content types, use the full available height
321+ lipgloss.Top,
184− p.contentViewPort.Height = maxContentHeight
322+ headerContent,
323+ contentFinal,
324+ form,
325+ )
185 }
326 }
186−
187− p.contentViewPort.SetContent(renderedContent)
188−
189− // Make focus change more apparent with different border styles and colors
190− var contentBorder lipgloss.Border
191− var borderColor lipgloss.TerminalColor
192−
193− if p.isViewportFocus {
194− contentBorder = lipgloss.DoubleBorder()
195− borderColor = styles.Blue
196− } else {
197− contentBorder = lipgloss.RoundedBorder()
198− borderColor = styles.Flamingo
199− }
200−
201− contentStyle := lipgloss.NewStyle().
202− MarginTop(1).
203− Padding(0, 1).
204− Border(contentBorder).
205− BorderForeground(borderColor)
206−
207− if p.isViewportFocus {
208− contentStyle = contentStyle.BorderBackground(styles.Surface0)
209− }
210−
211− contentFinal := contentStyle.Render(p.contentViewPort.View())
212− if renderedContent == "" {
213− contentFinal = ""
214− }
215−
216− return lipgloss.JoinVertical(
217− lipgloss.Top,
218− headerContent,
219− contentFinal,
220− form,
221− )
222}
327}
223
328
224func (p *permissionDialogCmp) View() string {
329func (p *permissionDialogCmp) View() string {