Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.

Commit f230c31

Browse files
committed
commands,model selector
1 parent e5ba0c3 commit f230c31

15 files changed

Lines changed: 523 additions & 740 deletions

File tree

internal/llm/models/models.go

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -34,44 +34,8 @@ const (
3434
ProviderMock ModelProvider = "__mock"
3535
)
3636

37-
// Providers in order of popularity
38-
var ProviderPopularity = map[ModelProvider]int{
39-
ProviderAnthropic: 1,
40-
ProviderOpenAI: 2,
41-
ProviderGemini: 3,
42-
ProviderGROQ: 4,
43-
ProviderOpenRouter: 5,
44-
ProviderBedrock: 6,
45-
ProviderAzure: 7,
46-
ProviderVertexAI: 8,
47-
}
48-
4937
var SupportedModels = map[ModelID]Model{
50-
//
51-
// // GEMINI
52-
// GEMINI25: {
53-
// ID: GEMINI25,
54-
// Name: "Gemini 2.5 Pro",
55-
// Provider: ProviderGemini,
56-
// APIModel: "gemini-2.5-pro-exp-03-25",
57-
// CostPer1MIn: 0,
58-
// CostPer1MInCached: 0,
59-
// CostPer1MOutCached: 0,
60-
// CostPer1MOut: 0,
61-
// },
62-
//
63-
// GRMINI20Flash: {
64-
// ID: GRMINI20Flash,
65-
// Name: "Gemini 2.0 Flash",
66-
// Provider: ProviderGemini,
67-
// APIModel: "gemini-2.0-flash",
68-
// CostPer1MIn: 0.1,
69-
// CostPer1MInCached: 0,
70-
// CostPer1MOutCached: 0.025,
71-
// CostPer1MOut: 0.4,
72-
// },
73-
//
74-
// // Bedrock
38+
// Bedrock
7539
BedrockClaude37Sonnet: {
7640
ID: BedrockClaude37Sonnet,
7741
Name: "Bedrock: Claude 3.7 Sonnet",

internal/tui/components/core/helpers.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ import (
1212
func Section(text string, width int) string {
1313
t := styles.CurrentTheme()
1414
char := "─"
15-
length := len(text) + 1
15+
length := lipgloss.Width(text) + 1
1616
remainingWidth := width - length
17+
lineStyle := t.S().Base.Foreground(t.Border)
1718
if remainingWidth > 0 {
18-
text = text + " " + t.S().Base.Foreground(t.Border).Render(strings.Repeat(char, remainingWidth))
19+
text = text + " " + lineStyle.Render(strings.Repeat(char, remainingWidth))
1920
}
2021
return text
2122
}
2223

2324
func Title(title string, width int) string {
2425
t := styles.CurrentTheme()
2526
char := "╱"
26-
length := len(title) + 1
27+
length := lipgloss.Width(title) + 1
2728
remainingWidth := width - length
2829
lineStyle := t.S().Base.Foreground(t.Primary)
2930
titleStyle := t.S().Base.Foreground(t.Primary)

internal/tui/components/core/list/list.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,11 +133,13 @@ type model struct {
133133
allItems []util.Model // The actual list items
134134
gapSize int // Number of empty lines between items
135135
padding []int // Padding around the list content
136+
wrapNavigation bool // Whether to wrap navigation at the ends
136137

137138
filterable bool // Whether items can be filtered
138139
filterPlaceholder string // Placeholder text for filter input
139140
filteredItems []util.Model // Filtered items based on current search
140141
input textinput.Model // Input field for filtering items
142+
inputStyle lipgloss.Style // Style for the input field
141143
hideFilterInput bool // Whether to hide the filter input field
142144
currentSearch string // Current search term for filtering
143145
}
@@ -204,10 +206,26 @@ func WithFilterPlaceholder(placeholder string) listOptions {
204206
}
205207
}
206208

209+
// WithInputStyle sets the style for the filter input field.
210+
func WithInputStyle(style lipgloss.Style) listOptions {
211+
return func(m *model) {
212+
m.inputStyle = style
213+
}
214+
}
215+
216+
// WithWrapNavigation enables wrapping navigation at the ends of the list.
217+
func WithWrapNavigation(wrap bool) listOptions {
218+
return func(m *model) {
219+
m.wrapNavigation = wrap
220+
}
221+
}
222+
207223
// New creates a new list model with the specified options.
208224
// The list starts with no items selected and requires SetItems to be called
209225
// or items to be provided via WithItems option.
210226
func New(opts ...listOptions) ListModel {
227+
t := styles.CurrentTheme()
228+
211229
m := &model{
212230
help: help.New(),
213231
keyMap: DefaultKeyMap(),
@@ -218,6 +236,7 @@ func New(opts ...listOptions) ListModel {
218236
padding: []int{},
219237
selectionState: selectionState{selectedIndex: NoSelection},
220238
filterPlaceholder: "Type to filter...",
239+
inputStyle: t.S().Base.Padding(0, 1, 1, 1),
221240
}
222241
for _, opt := range opts {
223242
opt(m)
@@ -281,7 +300,7 @@ func (m *model) View() tea.View {
281300
if m.filterable && !m.hideFilterInput {
282301
content = lipgloss.JoinVertical(
283302
lipgloss.Left,
284-
m.inputStyle().Render(m.input.View()),
303+
m.inputStyle.Render(m.input.View()),
285304
content,
286305
)
287306
}
@@ -400,7 +419,7 @@ func (m *model) renderVisibleForward() {
400419
renderer := &forwardRenderer{
401420
model: m,
402421
start: 0,
403-
cutoff: m.viewState.offset + m.listHeight(),
422+
cutoff: m.viewState.offset + m.listHeight() + m.listHeight()/2, // We render a bit more so we make sure we have smooth movementsd
404423
items: m.filteredItems,
405424
realIdx: m.renderState.lastIndex,
406425
}
@@ -420,7 +439,7 @@ func (m *model) renderVisibleReverse() {
420439
renderer := &reverseRenderer{
421440
model: m,
422441
start: 0,
423-
cutoff: m.viewState.offset + m.listHeight(),
442+
cutoff: m.viewState.offset + m.listHeight() + m.listHeight()/2,
424443
items: m.filteredItems,
425444
realIdx: m.renderState.lastIndex,
426445
}
@@ -567,6 +586,10 @@ func (r *reverseRenderer) renderItemLines(item util.Model) []string {
567586
// Handles focus management and ensures the selected item remains visible.
568587
// Skips section headers during navigation.
569588
func (m *model) selectPreviousItem() tea.Cmd {
589+
if m.selectionState.selectedIndex == m.findFirstSelectableItem() && m.wrapNavigation {
590+
// If at the beginning and wrapping is enabled, go to the last item
591+
return m.goToBottom()
592+
}
570593
if m.selectionState.selectedIndex <= 0 {
571594
return nil
572595
}
@@ -580,8 +603,9 @@ func (m *model) selectPreviousItem() tea.Cmd {
580603
}
581604

582605
// If we went past the beginning, stay at the first non-header item
583-
if m.selectionState.selectedIndex < 0 {
584-
m.selectionState.selectedIndex = m.findFirstSelectableItem()
606+
if m.selectionState.selectedIndex <= 0 {
607+
cmds = append(cmds, m.goToTop()) // Ensure we scroll to the top if needed
608+
return tea.Batch(cmds...)
585609
}
586610

587611
cmds = append(cmds, m.focusSelected())
@@ -593,6 +617,10 @@ func (m *model) selectPreviousItem() tea.Cmd {
593617
// Handles focus management and ensures the selected item remains visible.
594618
// Skips section headers during navigation.
595619
func (m *model) selectNextItem() tea.Cmd {
620+
if m.selectionState.selectedIndex >= m.findLastSelectableItem() && m.wrapNavigation {
621+
// If at the end and wrapping is enabled, go to the first item
622+
return m.goToTop()
623+
}
596624
if m.selectionState.selectedIndex >= len(m.filteredItems)-1 || m.selectionState.selectedIndex < 0 {
597625
return nil
598626
}
@@ -1008,6 +1036,9 @@ func (m *model) listHeight() int {
10081036
case 3, 4:
10091037
height -= m.padding[0] + m.padding[2]
10101038
}
1039+
if m.filterable && !m.hideFilterInput {
1040+
height -= lipgloss.Height(m.inputStyle.Render("dummy"))
1041+
}
10111042
return max(0, height)
10121043
}
10131044

@@ -1107,10 +1138,6 @@ func (m *model) SetItems(items []util.Model) tea.Cmd {
11071138
return tea.Batch(cmds...)
11081139
}
11091140

1110-
func (c *model) inputStyle() lipgloss.Style {
1111-
return styles.BaseStyle().Padding(0, 1, 1, 1)
1112-
}
1113-
11141141
// section represents a group of items under a section header.
11151142
type section struct {
11161143
header SectionHeader

internal/tui/components/dialog/filepicker.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
tea "github.com/charmbracelet/bubbletea/v2"
1616
"github.com/charmbracelet/lipgloss/v2"
1717
"github.com/opencode-ai/opencode/internal/app"
18-
"github.com/opencode-ai/opencode/internal/config"
1918
"github.com/opencode-ai/opencode/internal/logging"
2019
"github.com/opencode-ai/opencode/internal/message"
2120
"github.com/opencode-ai/opencode/internal/tui/image"
@@ -222,11 +221,11 @@ func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
222221
}
223222

224223
func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
225-
modeInfo := GetSelectedModel(config.Get())
226-
if !modeInfo.SupportsAttachments {
227-
logging.ErrorPersist(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name))
228-
return f, nil
229-
}
224+
// modeInfo := GetSelectedModel(config.Get())
225+
// if !modeInfo.SupportsAttachments {
226+
// logging.ErrorPersist(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name))
227+
// return f, nil
228+
// }
230229

231230
selectedFilePath := f.selectedFile
232231
if !isExtSupported(selectedFilePath) {

0 commit comments

Comments
 (0)