on main help sign in
Improve diff display with colored formatting in permission dialogs

- Add colored formatting for diff display (green for additions, red for removals) - Create a dedicated formatDiff function to handle diff styling - Restructure permission dialog render function for better organization - Apply custom styling to different line types in diffs ๐Ÿค– Generated with termai Co-Authored-By: termai <noreply@termai.io>

commitb28c1e0c03dbf12bf428f19214b10f4925c229fa authorKujtim Hoxha <kujtimii.h@gmail.com> dateFri 4 Apr 2025, 14:45 (2 years ago) parentef15976
1 file changed+171โˆ’66
internal/tui/components/dialog/permission.go+171โˆ’66
internal/tui/components/dialog/permission.go+171โˆ’66
@@ -65,6 +65,45 @@ type permissionDialogCmp struct {
6565 selectOption *huh.Select[string]
6666}
6767
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+
68107func (p *permissionDialogCmp) Init() tea.Cmd {
69108 return nil
70109}
@@ -132,93 +171,159 @@ func (p *permissionDialogCmp) render() string {
132171 lipgloss.JoinHorizontal(lipgloss.Left, keyStyle.Render("Path:"), " ", valueStyle.Render(p.permission.Path)),
133172 " ",
134173 }
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+
135178 r, _ := glamour.NewTermRenderer(
136179 glamour.WithStyles(styles.CatppuccinMarkdownStyle()),
137180 glamour.WithWordWrap(p.width-10),
138181 glamour.WithEmoji(),
139182 )
140โˆ’ content := ""
183+
184+ // Handle different tool types
141185 switch p.permission.ToolName {
142186 case tools.BashToolName:
143187 pr := p.permission.Params.(tools.BashPermissionsParams)
144188 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+
146243 case tools.EditToolName:
147244 pr := p.permission.Params.(tools.EditPermissionsParams)
148245 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+
150257 case tools.WriteToolName:
151258 pr := p.permission.Params.(tools.WritePermissionsParams)
152259 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+
154271 case tools.FetchToolName:
155272 pr := p.permission.Params.(tools.FetchPermissionsParams)
156273 headerParts = append(headerParts, keyStyle.Render("URL: "+pr.URL))
157โˆ’ default:
158โˆ’ content = p.permission.Description
159โˆ’ }
160โˆ’
161โˆ’ 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
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
170โˆ’
171โˆ’ // For bash commands, adjust height based on content length
172โˆ’ if p.permission.ToolName == tools.BashToolName {
173โˆ’ // Add some padding to the content lines
174โˆ’ contentHeight := contentLines + 2
175โˆ’ if contentHeight < minContentHeight {
176โˆ’ contentHeight = minContentHeight
274+ content := p.permission.Description
275+
276+ renderedContent, _ := r.Render(content)
277+ p.contentViewPort.Width = p.width - 2 - 2
278+ p.contentViewPort.Height = p.height - lipgloss.Height(headerContent) - lipgloss.Height(form) - 2 - 2 - 1
279+ p.contentViewPort.SetContent(renderedContent)
280+
281+ // Style the viewport
282+ contentStyle := lipgloss.NewStyle().
283+ MarginTop(1).
284+ Padding(0, 1).
285+ Border(lipgloss.RoundedBorder()).
286+ BorderForeground(styles.Flamingo)
287+
288+ contentFinal := contentStyle.Render(p.contentViewPort.View())
289+ if renderedContent == "" {
290+ contentFinal = ""
177291 }
178โˆ’ if contentHeight > maxContentHeight {
179โˆ’ contentHeight = maxContentHeight
292+
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 = ""
180318 }
181โˆ’ p.contentViewPort.Height = contentHeight
182โˆ’ } else {
183โˆ’ // For other content types, use the full available height
184โˆ’ p.contentViewPort.Height = maxContentHeight
319+
320+ return lipgloss.JoinVertical(
321+ lipgloss.Top,
322+ headerContent,
323+ contentFinal,
324+ form,
325+ )
185326 }
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โˆ’ )
222327}
223328
224329func (p *permissionDialogCmp) View() string {