summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorTimo Clasen <[email protected]>2025-07-15 18:20:56 +0200
committerGitHub <[email protected]>2025-07-15 11:20:56 -0500
commitf707fb3f8da2d1e67b8b7c610470acdb026871eb (patch)
treef1fdc45001dc9b93e4184684bc290fb53fe30c86 /packages
parent6b98acb7be04ed04a56ea69b4cdbdcc5b4788a75 (diff)
downloadopencode-f707fb3f8da2d1e67b8b7c610470acdb026871eb.tar.gz
opencode-f707fb3f8da2d1e67b8b7c610470acdb026871eb.zip
feat(tui): add keymap to remove entries from recently used models (#1019)
Diffstat (limited to 'packages')
-rw-r--r--packages/tui/internal/components/dialog/models.go36
-rw-r--r--packages/tui/internal/components/dialog/search.go18
-rw-r--r--packages/tui/internal/config/config.go9
3 files changed, 62 insertions, 1 deletions
diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go
index 34712f51c..8f1069fcb 100644
--- a/packages/tui/internal/components/dialog/models.go
+++ b/packages/tui/internal/components/dialog/models.go
@@ -23,6 +23,7 @@ const (
numVisibleModels = 10
minDialogWidth = 40
maxDialogWidth = 80
+ maxRecentModels = 5
)
// ModelDialog interface for the model selection dialog
@@ -122,6 +123,17 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case SearchCancelledMsg:
return m, util.CmdHandler(modal.CloseModalMsg{})
+ case SearchRemoveItemMsg:
+ if item, ok := msg.Item.(modelItem); ok {
+ if m.isModelInRecentSection(item.model, msg.Index) {
+ m.app.State.RemoveModelFromRecentlyUsed(item.model.Provider.ID, item.model.Model.ID)
+ m.app.SaveState()
+ items := m.buildDisplayList(m.searchDialog.GetQuery())
+ m.searchDialog.SetItems(items)
+ }
+ }
+ return m, nil
+
case SearchQueryChangedMsg:
// Update the list based on search query
items := m.buildDisplayList(msg.Query)
@@ -307,7 +319,7 @@ func (m *modelDialog) buildGroupedResults() []list.Item {
var items []list.Item
// Add Recent section
- recentModels := m.getRecentModels(5)
+ recentModels := m.getRecentModels(maxRecentModels)
if len(recentModels) > 0 {
items = append(items, list.HeaderItem("Recent"))
for _, model := range recentModels {
@@ -398,6 +410,28 @@ func (m *modelDialog) getRecentModels(limit int) []ModelWithProvider {
return recentModels
}
+func (m *modelDialog) isModelInRecentSection(model ModelWithProvider, index int) bool {
+ // Only check if we're in grouped mode (no search query)
+ if m.searchDialog.GetQuery() != "" {
+ return false
+ }
+
+ recentModels := m.getRecentModels(maxRecentModels)
+ if len(recentModels) == 0 {
+ return false
+ }
+
+ // Index 0 is the "Recent" header, so recent models are at indices 1 to len(recentModels)
+ if index >= 1 && index <= len(recentModels) {
+ if index-1 < len(recentModels) {
+ recentModel := recentModels[index-1]
+ return recentModel.Provider.ID == model.Provider.ID && recentModel.Model.ID == model.Model.ID
+ }
+ }
+
+ return false
+}
+
func (m *modelDialog) Render(background string) string {
return m.modal.Render(m.View(), background)
}
diff --git a/packages/tui/internal/components/dialog/search.go b/packages/tui/internal/components/dialog/search.go
index 13a305304..cdb2b824e 100644
--- a/packages/tui/internal/components/dialog/search.go
+++ b/packages/tui/internal/components/dialog/search.go
@@ -24,6 +24,12 @@ type SearchSelectionMsg struct {
// SearchCancelledMsg is emitted when the search is cancelled
type SearchCancelledMsg struct{}
+// SearchRemoveItemMsg is emitted when Ctrl+X is pressed to remove an item
+type SearchRemoveItemMsg struct {
+ Item any
+ Index int
+}
+
// SearchDialog is a reusable component that combines a text input with a list
type SearchDialog struct {
textInput textinput.Model
@@ -38,6 +44,7 @@ type searchKeyMap struct {
Down key.Binding
Enter key.Binding
Escape key.Binding
+ Remove key.Binding
}
var searchKeys = searchKeyMap{
@@ -57,6 +64,10 @@ var searchKeys = searchKeyMap{
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
),
+ Remove: key.NewBinding(
+ key.WithKeys("ctrl+x"),
+ key.WithHelp("ctrl+x", "remove from recent"),
+ ),
}
// NewSearchDialog creates a new SearchDialog
@@ -148,6 +159,13 @@ func (s *SearchDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
+ case key.Matches(msg, searchKeys.Remove):
+ if selectedItem, idx := s.list.GetSelectedItem(); idx != -1 {
+ return s, func() tea.Msg {
+ return SearchRemoveItemMsg{Item: selectedItem, Index: idx}
+ }
+ }
+
case key.Matches(msg, searchKeys.Up):
var cmd tea.Cmd
listModel, cmd := s.list.Update(msg)
diff --git a/packages/tui/internal/config/config.go b/packages/tui/internal/config/config.go
index 7004b85b1..d20376dd8 100644
--- a/packages/tui/internal/config/config.go
+++ b/packages/tui/internal/config/config.go
@@ -69,6 +69,15 @@ func (s *State) UpdateModelUsage(providerID, modelID string) {
}
}
+func (s *State) RemoveModelFromRecentlyUsed(providerID, modelID string) {
+ for i, usage := range s.RecentlyUsedModels {
+ if usage.ProviderID == providerID && usage.ModelID == modelID {
+ s.RecentlyUsedModels = append(s.RecentlyUsedModels[:i], s.RecentlyUsedModels[i+1:]...)
+ return
+ }
+ }
+}
+
// SaveState writes the provided Config struct to the specified TOML file.
// It will create the file if it doesn't exist, or overwrite it if it does.
func SaveState(filePath string, state *State) error {