1package permissions
2
3import (
4 "fmt"
5 "strings"
6
7 "github.com/charmbracelet/bubbles/v2/help"
8 "github.com/charmbracelet/bubbles/v2/key"
9 "github.com/charmbracelet/bubbles/v2/viewport"
10 tea "github.com/charmbracelet/bubbletea/v2"
11 "github.com/charmbracelet/crush/internal/fsext"
12 "github.com/charmbracelet/crush/internal/llm/tools"
13 "github.com/charmbracelet/crush/internal/permission"
14 "github.com/charmbracelet/crush/internal/tui/components/core"
15 "github.com/charmbracelet/crush/internal/tui/components/dialogs"
16 "github.com/charmbracelet/crush/internal/tui/styles"
17 "github.com/charmbracelet/crush/internal/tui/util"
18 "github.com/charmbracelet/lipgloss/v2"
19 "github.com/charmbracelet/x/ansi"
20)
21
22type PermissionAction string
23
24// Permission responses
25const (
26 PermissionAllow PermissionAction = "allow"
27 PermissionAllowForSession PermissionAction = "allow_session"
28 PermissionDeny PermissionAction = "deny"
29
30 PermissionsDialogID dialogs.DialogID = "permissions"
31)
32
33// PermissionResponseMsg represents the user's response to a permission request
34type PermissionResponseMsg struct {
35 Permission permission.PermissionRequest
36 Action PermissionAction
37}
38
39// PermissionDialogCmp interface for permission dialog component
40type PermissionDialogCmp interface {
41 dialogs.DialogModel
42}
43
44// permissionDialogCmp is the implementation of PermissionDialog
45type permissionDialogCmp struct {
46 wWidth int
47 wHeight int
48 width int
49 height int
50 permission permission.PermissionRequest
51 contentViewPort viewport.Model
52 selectedOption int // 0: Allow, 1: Allow for session, 2: Deny
53
54 // Diff view state
55 defaultDiffSplitMode bool // true for split, false for unified
56 diffSplitMode *bool // nil means use defaultDiffSplitMode
57 diffXOffset int // horizontal scroll offset
58 diffYOffset int // vertical scroll offset
59
60 // Caching
61 cachedContent string
62 contentDirty bool
63
64 positionRow int // Row position for dialog
65 positionCol int // Column position for dialog
66
67 keyMap KeyMap
68}
69
70func NewPermissionDialogCmp(permission permission.PermissionRequest) PermissionDialogCmp {
71 // Create viewport for content
72 contentViewport := viewport.New()
73 return &permissionDialogCmp{
74 contentViewPort: contentViewport,
75 selectedOption: 0, // Default to "Allow"
76 permission: permission,
77 keyMap: DefaultKeyMap(),
78 contentDirty: true, // Mark as dirty initially
79 }
80}
81
82func (p *permissionDialogCmp) Init() tea.Cmd {
83 return p.contentViewPort.Init()
84}
85
86func (p *permissionDialogCmp) supportsDiffView() bool {
87 return p.permission.ToolName == tools.EditToolName || p.permission.ToolName == tools.WriteToolName || p.permission.ToolName == tools.MultiEditToolName
88}
89
90func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91 var cmds []tea.Cmd
92
93 switch msg := msg.(type) {
94 case tea.WindowSizeMsg:
95 p.wWidth = msg.Width
96 p.wHeight = msg.Height
97 p.contentDirty = true // Mark content as dirty on window resize
98 cmd := p.SetSize()
99 cmds = append(cmds, cmd)
100 case tea.KeyPressMsg:
101 switch {
102 case key.Matches(msg, p.keyMap.Right) || key.Matches(msg, p.keyMap.Tab):
103 p.selectedOption = (p.selectedOption + 1) % 3
104 return p, nil
105 case key.Matches(msg, p.keyMap.Left):
106 p.selectedOption = (p.selectedOption + 2) % 3
107 case key.Matches(msg, p.keyMap.Select):
108 return p, p.selectCurrentOption()
109 case key.Matches(msg, p.keyMap.Allow):
110 return p, tea.Batch(
111 util.CmdHandler(dialogs.CloseDialogMsg{}),
112 util.CmdHandler(PermissionResponseMsg{Action: PermissionAllow, Permission: p.permission}),
113 )
114 case key.Matches(msg, p.keyMap.AllowSession):
115 return p, tea.Batch(
116 util.CmdHandler(dialogs.CloseDialogMsg{}),
117 util.CmdHandler(PermissionResponseMsg{Action: PermissionAllowForSession, Permission: p.permission}),
118 )
119 case key.Matches(msg, p.keyMap.Deny):
120 return p, tea.Batch(
121 util.CmdHandler(dialogs.CloseDialogMsg{}),
122 util.CmdHandler(PermissionResponseMsg{Action: PermissionDeny, Permission: p.permission}),
123 )
124 case key.Matches(msg, p.keyMap.ToggleDiffMode):
125 if p.supportsDiffView() {
126 if p.diffSplitMode == nil {
127 diffSplitMode := !p.defaultDiffSplitMode
128 p.diffSplitMode = &diffSplitMode
129 } else {
130 *p.diffSplitMode = !*p.diffSplitMode
131 }
132 p.contentDirty = true // Mark content as dirty when diff mode changes
133 return p, nil
134 }
135 case key.Matches(msg, p.keyMap.ScrollDown):
136 if p.supportsDiffView() {
137 p.scrollDown()
138 return p, nil
139 }
140 case key.Matches(msg, p.keyMap.ScrollUp):
141 if p.supportsDiffView() {
142 p.scrollUp()
143 return p, nil
144 }
145 case key.Matches(msg, p.keyMap.ScrollLeft):
146 if p.supportsDiffView() {
147 p.scrollLeft()
148 return p, nil
149 }
150 case key.Matches(msg, p.keyMap.ScrollRight):
151 if p.supportsDiffView() {
152 p.scrollRight()
153 return p, nil
154 }
155 default:
156 // Pass other keys to viewport
157 viewPort, cmd := p.contentViewPort.Update(msg)
158 p.contentViewPort = viewPort
159 cmds = append(cmds, cmd)
160 }
161 case tea.MouseWheelMsg:
162 if p.supportsDiffView() {
163 switch msg.Button {
164 case tea.MouseWheelDown:
165 p.scrollDown()
166 case tea.MouseWheelUp:
167 p.scrollUp()
168 case tea.MouseWheelLeft:
169 p.scrollLeft()
170 case tea.MouseWheelRight:
171 p.scrollRight()
172 }
173 }
174 }
175
176 return p, tea.Batch(cmds...)
177}
178
179func (p *permissionDialogCmp) scrollDown() {
180 p.diffYOffset += 1
181 p.contentDirty = true
182}
183
184func (p *permissionDialogCmp) scrollUp() {
185 p.diffYOffset = max(0, p.diffYOffset-1)
186 p.contentDirty = true
187}
188
189func (p *permissionDialogCmp) scrollLeft() {
190 p.diffXOffset = max(0, p.diffXOffset-5)
191 p.contentDirty = true
192}
193
194func (p *permissionDialogCmp) scrollRight() {
195 p.diffXOffset += 5
196 p.contentDirty = true
197}
198
199func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd {
200 var action PermissionAction
201
202 switch p.selectedOption {
203 case 0:
204 action = PermissionAllow
205 case 1:
206 action = PermissionAllowForSession
207 case 2:
208 action = PermissionDeny
209 }
210
211 return tea.Batch(
212 util.CmdHandler(PermissionResponseMsg{Action: action, Permission: p.permission}),
213 util.CmdHandler(dialogs.CloseDialogMsg{}),
214 )
215}
216
217func (p *permissionDialogCmp) renderButtons() string {
218 t := styles.CurrentTheme()
219 baseStyle := t.S().Base
220
221 buttons := []core.ButtonOpts{
222 {
223 Text: "Allow",
224 UnderlineIndex: 0, // "A"
225 Selected: p.selectedOption == 0,
226 },
227 {
228 Text: "Allow for Session",
229 UnderlineIndex: 10, // "S" in "Session"
230 Selected: p.selectedOption == 1,
231 },
232 {
233 Text: "Deny",
234 UnderlineIndex: 0, // "D"
235 Selected: p.selectedOption == 2,
236 },
237 }
238
239 content := core.SelectableButtons(buttons, " ")
240 if lipgloss.Width(content) > p.width-4 {
241 content = core.SelectableButtonsVertical(buttons, 1)
242 return baseStyle.AlignVertical(lipgloss.Center).
243 AlignHorizontal(lipgloss.Center).
244 Width(p.width - 4).
245 Render(content)
246 }
247
248 return baseStyle.AlignHorizontal(lipgloss.Right).Width(p.width - 4).Render(content)
249}
250
251func (p *permissionDialogCmp) renderHeader() string {
252 t := styles.CurrentTheme()
253 baseStyle := t.S().Base
254
255 toolKey := t.S().Muted.Render("Tool")
256 toolValue := t.S().Text.
257 Width(p.width - lipgloss.Width(toolKey)).
258 Render(fmt.Sprintf(" %s", p.permission.ToolName))
259
260 pathKey := t.S().Muted.Render("Path")
261 pathValue := t.S().Text.
262 Width(p.width - lipgloss.Width(pathKey)).
263 Render(fmt.Sprintf(" %s", fsext.PrettyPath(p.permission.Path)))
264
265 headerParts := []string{
266 lipgloss.JoinHorizontal(
267 lipgloss.Left,
268 toolKey,
269 toolValue,
270 ),
271 baseStyle.Render(strings.Repeat(" ", p.width)),
272 lipgloss.JoinHorizontal(
273 lipgloss.Left,
274 pathKey,
275 pathValue,
276 ),
277 baseStyle.Render(strings.Repeat(" ", p.width)),
278 }
279
280 // Add tool-specific header information
281 switch p.permission.ToolName {
282 case tools.BashToolName:
283 headerParts = append(headerParts, t.S().Muted.Width(p.width).Render("Command"))
284 case tools.DownloadToolName:
285 params := p.permission.Params.(tools.DownloadPermissionsParams)
286 urlKey := t.S().Muted.Render("URL")
287 urlValue := t.S().Text.
288 Width(p.width - lipgloss.Width(urlKey)).
289 Render(fmt.Sprintf(" %s", params.URL))
290 fileKey := t.S().Muted.Render("File")
291 filePath := t.S().Text.
292 Width(p.width - lipgloss.Width(fileKey)).
293 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
294 headerParts = append(headerParts,
295 lipgloss.JoinHorizontal(
296 lipgloss.Left,
297 urlKey,
298 urlValue,
299 ),
300 baseStyle.Render(strings.Repeat(" ", p.width)),
301 lipgloss.JoinHorizontal(
302 lipgloss.Left,
303 fileKey,
304 filePath,
305 ),
306 baseStyle.Render(strings.Repeat(" ", p.width)),
307 )
308 case tools.EditToolName:
309 params := p.permission.Params.(tools.EditPermissionsParams)
310 fileKey := t.S().Muted.Render("File")
311 filePath := t.S().Text.
312 Width(p.width - lipgloss.Width(fileKey)).
313 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
314 headerParts = append(headerParts,
315 lipgloss.JoinHorizontal(
316 lipgloss.Left,
317 fileKey,
318 filePath,
319 ),
320 baseStyle.Render(strings.Repeat(" ", p.width)),
321 )
322
323 case tools.WriteToolName:
324 params := p.permission.Params.(tools.WritePermissionsParams)
325 fileKey := t.S().Muted.Render("File")
326 filePath := t.S().Text.
327 Width(p.width - lipgloss.Width(fileKey)).
328 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
329 headerParts = append(headerParts,
330 lipgloss.JoinHorizontal(
331 lipgloss.Left,
332 fileKey,
333 filePath,
334 ),
335 baseStyle.Render(strings.Repeat(" ", p.width)),
336 )
337 case tools.MultiEditToolName:
338 params := p.permission.Params.(tools.MultiEditPermissionsParams)
339 fileKey := t.S().Muted.Render("File")
340 filePath := t.S().Text.
341 Width(p.width - lipgloss.Width(fileKey)).
342 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
343 headerParts = append(headerParts,
344 lipgloss.JoinHorizontal(
345 lipgloss.Left,
346 fileKey,
347 filePath,
348 ),
349 baseStyle.Render(strings.Repeat(" ", p.width)),
350 )
351 case tools.FetchToolName:
352 headerParts = append(headerParts, t.S().Muted.Width(p.width).Bold(true).Render("URL"))
353 case tools.ViewToolName:
354 params := p.permission.Params.(tools.ViewPermissionsParams)
355 fileKey := t.S().Muted.Render("File")
356 filePath := t.S().Text.
357 Width(p.width - lipgloss.Width(fileKey)).
358 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.FilePath)))
359 headerParts = append(headerParts,
360 lipgloss.JoinHorizontal(
361 lipgloss.Left,
362 fileKey,
363 filePath,
364 ),
365 baseStyle.Render(strings.Repeat(" ", p.width)),
366 )
367 case tools.LSToolName:
368 params := p.permission.Params.(tools.LSPermissionsParams)
369 pathKey := t.S().Muted.Render("Directory")
370 pathValue := t.S().Text.
371 Width(p.width - lipgloss.Width(pathKey)).
372 Render(fmt.Sprintf(" %s", fsext.PrettyPath(params.Path)))
373 headerParts = append(headerParts,
374 lipgloss.JoinHorizontal(
375 lipgloss.Left,
376 pathKey,
377 pathValue,
378 ),
379 baseStyle.Render(strings.Repeat(" ", p.width)),
380 )
381 }
382
383 return baseStyle.Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...))
384}
385
386func (p *permissionDialogCmp) getOrGenerateContent() string {
387 // Return cached content if available and not dirty
388 if !p.contentDirty && p.cachedContent != "" {
389 return p.cachedContent
390 }
391
392 // Generate new content
393 var content string
394 switch p.permission.ToolName {
395 case tools.BashToolName:
396 content = p.generateBashContent()
397 case tools.DownloadToolName:
398 content = p.generateDownloadContent()
399 case tools.EditToolName:
400 content = p.generateEditContent()
401 case tools.WriteToolName:
402 content = p.generateWriteContent()
403 case tools.MultiEditToolName:
404 content = p.generateMultiEditContent()
405 case tools.FetchToolName:
406 content = p.generateFetchContent()
407 case tools.ViewToolName:
408 content = p.generateViewContent()
409 case tools.LSToolName:
410 content = p.generateLSContent()
411 default:
412 content = p.generateDefaultContent()
413 }
414
415 // Cache the result
416 p.cachedContent = content
417 p.contentDirty = false
418
419 return content
420}
421
422func (p *permissionDialogCmp) generateBashContent() string {
423 t := styles.CurrentTheme()
424 baseStyle := t.S().Base.Background(t.BgSubtle)
425 if pr, ok := p.permission.Params.(tools.BashPermissionsParams); ok {
426 content := pr.Command
427 t := styles.CurrentTheme()
428 content = strings.TrimSpace(content)
429 lines := strings.Split(content, "\n")
430
431 width := p.width - 4
432 var out []string
433 for _, ln := range lines {
434 out = append(out, t.S().Muted.
435 Width(width).
436 Padding(0, 3).
437 Foreground(t.FgBase).
438 Background(t.BgSubtle).
439 Render(ln))
440 }
441
442 // Use the cache for markdown rendering
443 renderedContent := strings.Join(out, "\n")
444 finalContent := baseStyle.
445 Width(p.contentViewPort.Width()).
446 Padding(1, 0).
447 Render(renderedContent)
448
449 return finalContent
450 }
451 return ""
452}
453
454func (p *permissionDialogCmp) generateEditContent() string {
455 if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok {
456 formatter := core.DiffFormatter().
457 Before(fsext.PrettyPath(pr.FilePath), pr.OldContent).
458 After(fsext.PrettyPath(pr.FilePath), pr.NewContent).
459 Height(p.contentViewPort.Height()).
460 Width(p.contentViewPort.Width()).
461 XOffset(p.diffXOffset).
462 YOffset(p.diffYOffset)
463 if p.useDiffSplitMode() {
464 formatter = formatter.Split()
465 } else {
466 formatter = formatter.Unified()
467 }
468
469 diff := formatter.String()
470 return diff
471 }
472 return ""
473}
474
475func (p *permissionDialogCmp) generateWriteContent() string {
476 if pr, ok := p.permission.Params.(tools.WritePermissionsParams); ok {
477 // Use the cache for diff rendering
478 formatter := core.DiffFormatter().
479 Before(fsext.PrettyPath(pr.FilePath), pr.OldContent).
480 After(fsext.PrettyPath(pr.FilePath), pr.NewContent).
481 Height(p.contentViewPort.Height()).
482 Width(p.contentViewPort.Width()).
483 XOffset(p.diffXOffset).
484 YOffset(p.diffYOffset)
485 if p.useDiffSplitMode() {
486 formatter = formatter.Split()
487 } else {
488 formatter = formatter.Unified()
489 }
490
491 diff := formatter.String()
492 return diff
493 }
494 return ""
495}
496
497func (p *permissionDialogCmp) generateDownloadContent() string {
498 t := styles.CurrentTheme()
499 baseStyle := t.S().Base.Background(t.BgSubtle)
500 if pr, ok := p.permission.Params.(tools.DownloadPermissionsParams); ok {
501 content := fmt.Sprintf("URL: %s\nFile: %s", pr.URL, fsext.PrettyPath(pr.FilePath))
502 if pr.Timeout > 0 {
503 content += fmt.Sprintf("\nTimeout: %ds", pr.Timeout)
504 }
505
506 finalContent := baseStyle.
507 Padding(1, 2).
508 Width(p.contentViewPort.Width()).
509 Render(content)
510 return finalContent
511 }
512 return ""
513}
514
515func (p *permissionDialogCmp) generateMultiEditContent() string {
516 if pr, ok := p.permission.Params.(tools.MultiEditPermissionsParams); ok {
517 // Use the cache for diff rendering
518 formatter := core.DiffFormatter().
519 Before(fsext.PrettyPath(pr.FilePath), pr.OldContent).
520 After(fsext.PrettyPath(pr.FilePath), pr.NewContent).
521 Height(p.contentViewPort.Height()).
522 Width(p.contentViewPort.Width()).
523 XOffset(p.diffXOffset).
524 YOffset(p.diffYOffset)
525 if p.useDiffSplitMode() {
526 formatter = formatter.Split()
527 } else {
528 formatter = formatter.Unified()
529 }
530
531 diff := formatter.String()
532 return diff
533 }
534 return ""
535}
536
537func (p *permissionDialogCmp) generateFetchContent() string {
538 t := styles.CurrentTheme()
539 baseStyle := t.S().Base.Background(t.BgSubtle)
540 if pr, ok := p.permission.Params.(tools.FetchPermissionsParams); ok {
541 finalContent := baseStyle.
542 Padding(1, 2).
543 Width(p.contentViewPort.Width()).
544 Render(pr.URL)
545 return finalContent
546 }
547 return ""
548}
549
550func (p *permissionDialogCmp) generateViewContent() string {
551 t := styles.CurrentTheme()
552 baseStyle := t.S().Base.Background(t.BgSubtle)
553 if pr, ok := p.permission.Params.(tools.ViewPermissionsParams); ok {
554 content := fmt.Sprintf("File: %s", fsext.PrettyPath(pr.FilePath))
555 if pr.Offset > 0 {
556 content += fmt.Sprintf("\nStarting from line: %d", pr.Offset+1)
557 }
558 if pr.Limit > 0 && pr.Limit != 2000 { // 2000 is the default limit
559 content += fmt.Sprintf("\nLines to read: %d", pr.Limit)
560 }
561
562 finalContent := baseStyle.
563 Padding(1, 2).
564 Width(p.contentViewPort.Width()).
565 Render(content)
566 return finalContent
567 }
568 return ""
569}
570
571func (p *permissionDialogCmp) generateLSContent() string {
572 t := styles.CurrentTheme()
573 baseStyle := t.S().Base.Background(t.BgSubtle)
574 if pr, ok := p.permission.Params.(tools.LSPermissionsParams); ok {
575 content := fmt.Sprintf("Directory: %s", fsext.PrettyPath(pr.Path))
576 if len(pr.Ignore) > 0 {
577 content += fmt.Sprintf("\nIgnore patterns: %s", strings.Join(pr.Ignore, ", "))
578 }
579
580 finalContent := baseStyle.
581 Padding(1, 2).
582 Width(p.contentViewPort.Width()).
583 Render(content)
584 return finalContent
585 }
586 return ""
587}
588
589func (p *permissionDialogCmp) generateDefaultContent() string {
590 t := styles.CurrentTheme()
591 baseStyle := t.S().Base.Background(t.BgSubtle)
592
593 content := p.permission.Description
594
595 content = strings.TrimSpace(content)
596 content = "\n" + content + "\n"
597 lines := strings.Split(content, "\n")
598
599 width := p.width - 4
600 var out []string
601 for _, ln := range lines {
602 ln = " " + ln // left padding
603 if len(ln) > width {
604 ln = ansi.Truncate(ln, width, "…")
605 }
606 out = append(out, t.S().Muted.
607 Width(width).
608 Foreground(t.FgBase).
609 Background(t.BgSubtle).
610 Render(ln))
611 }
612
613 // Use the cache for markdown rendering
614 renderedContent := strings.Join(out, "\n")
615 finalContent := baseStyle.
616 Width(p.contentViewPort.Width()).
617 Render(renderedContent)
618
619 if renderedContent == "" {
620 return ""
621 }
622
623 return finalContent
624}
625
626func (p *permissionDialogCmp) useDiffSplitMode() bool {
627 if p.diffSplitMode != nil {
628 return *p.diffSplitMode
629 } else {
630 return p.defaultDiffSplitMode
631 }
632}
633
634func (p *permissionDialogCmp) styleViewport() string {
635 t := styles.CurrentTheme()
636 return t.S().Base.Render(p.contentViewPort.View())
637}
638
639func (p *permissionDialogCmp) render() string {
640 t := styles.CurrentTheme()
641 baseStyle := t.S().Base
642 title := core.Title("Permission Required", p.width-4)
643 // Render header
644 headerContent := p.renderHeader()
645 // Render buttons
646 buttons := p.renderButtons()
647
648 p.contentViewPort.SetWidth(p.width - 4)
649
650 // Get cached or generate content
651 contentFinal := p.getOrGenerateContent()
652
653 // Always set viewport content (the caching is handled in getOrGenerateContent)
654 const minContentHeight = 9
655 contentHeight := min(
656 max(minContentHeight, p.height-minContentHeight),
657 lipgloss.Height(contentFinal),
658 )
659 p.contentViewPort.SetHeight(contentHeight)
660 p.contentViewPort.SetContent(contentFinal)
661
662 p.positionRow = p.wHeight / 2
663 p.positionRow -= (contentHeight + 9) / 2
664 p.positionRow -= 3 // Move dialog slightly higher than middle
665
666 var contentHelp string
667 if p.supportsDiffView() {
668 contentHelp = help.New().View(p.keyMap)
669 }
670
671 // Calculate content height dynamically based on window size
672 strs := []string{
673 title,
674 "",
675 headerContent,
676 p.styleViewport(),
677 "",
678 buttons,
679 "",
680 }
681 if contentHelp != "" {
682 strs = append(strs, "", contentHelp)
683 }
684 content := lipgloss.JoinVertical(lipgloss.Top, strs...)
685
686 return baseStyle.
687 Padding(0, 1).
688 Border(lipgloss.RoundedBorder()).
689 BorderForeground(t.BorderFocus).
690 Width(p.width).
691 Render(
692 content,
693 )
694}
695
696func (p *permissionDialogCmp) View() string {
697 return p.render()
698}
699
700func (p *permissionDialogCmp) SetSize() tea.Cmd {
701 if p.permission.ID == "" {
702 return nil
703 }
704
705 oldWidth, oldHeight := p.width, p.height
706
707 switch p.permission.ToolName {
708 case tools.BashToolName:
709 p.width = int(float64(p.wWidth) * 0.8)
710 p.height = int(float64(p.wHeight) * 0.3)
711 case tools.DownloadToolName:
712 p.width = int(float64(p.wWidth) * 0.8)
713 p.height = int(float64(p.wHeight) * 0.4)
714 case tools.EditToolName:
715 p.width = int(float64(p.wWidth) * 0.8)
716 p.height = int(float64(p.wHeight) * 0.8)
717 case tools.WriteToolName:
718 p.width = int(float64(p.wWidth) * 0.8)
719 p.height = int(float64(p.wHeight) * 0.8)
720 case tools.MultiEditToolName:
721 p.width = int(float64(p.wWidth) * 0.8)
722 p.height = int(float64(p.wHeight) * 0.8)
723 case tools.FetchToolName:
724 p.width = int(float64(p.wWidth) * 0.8)
725 p.height = int(float64(p.wHeight) * 0.3)
726 case tools.ViewToolName:
727 p.width = int(float64(p.wWidth) * 0.8)
728 p.height = int(float64(p.wHeight) * 0.4)
729 case tools.LSToolName:
730 p.width = int(float64(p.wWidth) * 0.8)
731 p.height = int(float64(p.wHeight) * 0.4)
732 default:
733 p.width = int(float64(p.wWidth) * 0.7)
734 p.height = int(float64(p.wHeight) * 0.5)
735 }
736
737 // Default to diff split mode when dialog is wide enough.
738 p.defaultDiffSplitMode = p.width >= 140
739
740 // Set a maximum width for the dialog
741 p.width = min(p.width, 180)
742
743 // Mark content as dirty if size changed
744 if oldWidth != p.width || oldHeight != p.height {
745 p.contentDirty = true
746 }
747 p.positionRow = p.wHeight / 2
748 p.positionRow -= p.height / 2
749 p.positionRow -= 3 // Move dialog slightly higher than middle
750 p.positionCol = p.wWidth / 2
751 p.positionCol -= p.width / 2
752 return nil
753}
754
755func (c *permissionDialogCmp) GetOrSetMarkdown(key string, generator func() (string, error)) string {
756 content, err := generator()
757 if err != nil {
758 return fmt.Sprintf("Error rendering markdown: %v", err)
759 }
760
761 return content
762}
763
764// ID implements PermissionDialogCmp.
765func (p *permissionDialogCmp) ID() dialogs.DialogID {
766 return PermissionsDialogID
767}
768
769// Position implements PermissionDialogCmp.
770func (p *permissionDialogCmp) Position() (int, int) {
771 return p.positionRow, p.positionCol
772}