Skip to content

Commit 2afde76

Browse files
feat: implement plugin management menu and associated keybindings
1 parent 3499acb commit 2afde76

4 files changed

Lines changed: 85 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.1.2]
9+
10+
### Added
11+
- **Plugin Metadata Display**: Press `Enter` on a plugin in the menu to view full metadata including Name, Description, Author, and Version.
12+
- **Uninstall Confirmation**: Added safety confirmation dialog before uninstalling plugins with `u` key.
13+
814
## [1.1.1]
915

1016
### Added

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ Kairo features a **minimalist design system** optimized for clarity and focus.
175175
| `?` | ❓ Show help menu |
176176
| `q` | ❌ Quit |
177177

178+
### Plugin Menu Shortcuts
179+
180+
| Shortcut | Action |
181+
|----------|--------|
182+
| `enter` | 👁️ View plugin details |
183+
| `u` | 🗑️ Uninstall plugin |
184+
| `o` | 📂 Open plugins folder |
185+
| `r` | 🔄 Reload plugins |
186+
| `p` / `esc` | ❌ Close menu |
187+
178188
### View Shortcuts
179189

180190
| Shortcut | View |

internal/app/model.go

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ const (
8282
ModeHelp
8383
ModeThemeMenu
8484
ModePluginMenu
85-
ModePluginUninstall
8685
ModeTagFilter
8786
)
8887

@@ -123,8 +122,6 @@ type Model struct {
123122
all []core.Task
124123
tags []string
125124

126-
uninstallPluginID string
127-
128125
statusText string
129126
isErr bool
130127

@@ -314,11 +311,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
314311
}
315312
return m, nil
316313

317-
case plugin_menu.UninstallConfirmMsg:
318-
m.uninstallPluginID = x.ID
319-
m.mode = ModePluginUninstall
320-
return m, nil
321-
322314
case plugin_menu.UninstallMsg:
323315
if m.plugHost != nil {
324316
err := m.plugHost.DeletePlugin(x.ID)
@@ -333,6 +325,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
333325
m.rebuildViews()
334326
m.rebuildPaletteIndex()
335327
}
328+
if m.mode == ModePluginMenu {
329+
// Stay in plugin menu, refresh handled by SetPlugins above
330+
} else {
331+
m.mode = ModeList
332+
}
336333
return m, nil
337334

338335
case plugin_menu.OpenFolderMsg:
@@ -452,17 +449,6 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
452449
}
453450

454451
if km, ok := msg.(tea.KeyMsg); ok {
455-
if m.mode == ModePluginUninstall {
456-
switch km.String() {
457-
case "y", "enter":
458-
m.mode = ModePluginMenu
459-
return m, func() tea.Msg { return plugin_menu.UninstallMsg{ID: m.uninstallPluginID} }
460-
case "n", "esc":
461-
m.mode = ModePluginMenu
462-
return m, nil
463-
}
464-
}
465-
466452
if m.mode == ModeConfirmDelete {
467453
switch km.String() {
468454
case "y", "enter":
@@ -727,7 +713,7 @@ func (m *Model) View() string {
727713
content = m.hlp.View()
728714
case ModeThemeMenu:
729715
content = m.tm.View()
730-
case ModePluginMenu, ModePluginUninstall:
716+
case ModePluginMenu:
731717
content = m.renderMainUI()
732718
default:
733719
content = m.renderMainUI()
@@ -763,7 +749,7 @@ func (m *Model) renderMainUI() string {
763749
body = m.list.View()
764750
case ModeDetail:
765751
body = m.det.View()
766-
case ModePluginMenu, ModePluginUninstall:
752+
case ModePluginMenu:
767753
body = m.pm.View()
768754
default:
769755
body = m.list.View()
@@ -892,9 +878,7 @@ func (m *Model) renderFooter() string {
892878
case ModeThemeMenu:
893879
left = " " + m.s.Muted.Render("enter select • esc/q/"+fk(m.km.CycleTheme)+" close • ↑/↓ navigate")
894880
case ModePluginMenu:
895-
left = " " + m.s.Muted.Render("x uninstall • esc/q/"+fk(m.km.ManagePlugins)+" close • ↑/↓ navigate")
896-
case ModePluginUninstall:
897-
left = m.s.BadgeBad.Render(" UNINSTALL? ") + " " + m.s.Muted.Render("y/enter confirm • n/esc cancel")
881+
left = " " + m.s.Muted.Render("enter detail • u uninstall • o open folder • r reload • p/"+fk(m.km.ManagePlugins)+" close • ↑/↓ navigate")
898882
default:
899883
left = " " + m.s.Muted.Render(
900884
fk(m.km.Palette)+" "+styles.IconPalette+" • "+

internal/ui/plugin_menu/model.go

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ type UninstallMsg struct{ ID string }
1313
type OpenFolderMsg struct{}
1414
type ReloadMsg struct{}
1515

16-
type UninstallConfirmMsg struct{ ID string }
17-
1816
type Model struct {
1917
styles styles.Styles
2018
width int
2119
height int
2220
plugins []plugins.PluginInfo
2321
sel int
22+
detail *plugins.PluginInfo // nil means in list mode
23+
confirm string // non-empty means showing confirm for uninstalling this ID
2424
}
2525

2626
func New(s styles.Styles) Model {
@@ -44,6 +44,27 @@ func (m *Model) SetPlugins(ps []plugins.PluginInfo) {
4444
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
4545
switch x := msg.(type) {
4646
case tea.KeyMsg:
47+
if m.confirm != "" {
48+
switch x.String() {
49+
case "y":
50+
id := m.confirm
51+
m.confirm = ""
52+
return m, func() tea.Msg { return UninstallMsg{ID: id} }
53+
case "n", "esc":
54+
m.confirm = ""
55+
return m, nil
56+
}
57+
return m, nil
58+
}
59+
60+
if m.detail != nil {
61+
switch x.String() {
62+
case "esc", "q", "enter":
63+
m.detail = nil
64+
}
65+
return m, nil
66+
}
67+
4768
switch x.String() {
4869
case "esc", "q", "p":
4970
return m, func() tea.Msg { return CloseMsg{} }
@@ -55,14 +76,18 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
5576
if m.sel < len(m.plugins)-1 {
5677
m.sel++
5778
}
58-
case "o": // Open folder
79+
case "enter":
80+
if m.sel >= 0 && m.sel < len(m.plugins) {
81+
p := m.plugins[m.sel]
82+
m.detail = &p
83+
}
84+
case "o":
5985
return m, func() tea.Msg { return OpenFolderMsg{} }
60-
case "r": // Reload
86+
case "r":
6187
return m, func() tea.Msg { return ReloadMsg{} }
62-
case "x": // Uninstall key
88+
case "u":
6389
if m.sel >= 0 && m.sel < len(m.plugins) {
64-
id := m.plugins[m.sel].ID
65-
return m, func() tea.Msg { return UninstallConfirmMsg{ID: id} }
90+
m.confirm = m.plugins[m.sel].ID
6691
}
6792
}
6893
}
@@ -76,6 +101,35 @@ func (m Model) View() string {
76101
}
77102
cardW := min(80, w-4)
78103

104+
if m.confirm != "" {
105+
return lipgloss.Place(w, m.height, lipgloss.Center, lipgloss.Center,
106+
m.styles.Overlay.Width(cardW).Render(
107+
lipgloss.JoinVertical(lipgloss.Center,
108+
"Uninstall plugin?",
109+
"",
110+
m.styles.Accent.Render("[y] Yes [n] No"),
111+
),
112+
),
113+
)
114+
}
115+
116+
if m.detail != nil {
117+
p := m.detail
118+
content := lipgloss.JoinVertical(lipgloss.Left,
119+
m.styles.Title.Render(p.Name),
120+
"",
121+
"Version: "+p.Version,
122+
"Author: "+p.Author,
123+
"",
124+
p.Description,
125+
"",
126+
m.styles.Muted.Render("Press Enter/Esc to return"),
127+
)
128+
return lipgloss.Place(w, m.height, lipgloss.Center, lipgloss.Center,
129+
m.styles.Overlay.Width(cardW).Padding(2).Render(content),
130+
)
131+
}
132+
79133
var rows []string
80134
if len(m.plugins) == 0 {
81135
rows = append(rows, m.styles.Muted.Padding(0, 1).Render("No plugins."))

0 commit comments

Comments
 (0)