Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pkg/tui/components/editor/editor.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/docker/docker-agent/pkg/tui/components/editor/completions"
"github.com/docker/docker-agent/pkg/tui/core"
"github.com/docker/docker-agent/pkg/tui/core/layout"
"github.com/docker/docker-agent/pkg/tui/internal/termfeatures"
"github.com/docker/docker-agent/pkg/tui/messages"
"github.com/docker/docker-agent/pkg/tui/styles"
)
Expand Down Expand Up @@ -195,7 +196,7 @@ func New(hist *history.History, opts ...Option) Editor {
textarea: ta,
searchInput: si,
hist: hist,
keyboardEnhancementsSupported: false,
keyboardEnhancementsSupported: termfeatures.SupportsModifiedEnter(os.Getenv),
banner: newAttachmentBanner(),
}

Expand Down Expand Up @@ -634,7 +635,7 @@ func (e *editor) Update(msg tea.Msg) (layout.Model, tea.Cmd) {
}
case tea.KeyboardEnhancementsMsg:
// Track keyboard enhancement support and configure newline keybinding accordingly
e.keyboardEnhancementsSupported = msg.Flags != 0
e.keyboardEnhancementsSupported = msg.Flags != 0 || termfeatures.SupportsModifiedEnter(os.Getenv)
e.configureNewlineKeybinding()
return e, nil
case messages.ThemeChangedMsg:
Expand Down
19 changes: 19 additions & 0 deletions pkg/tui/internal/termfeatures/keyboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package termfeatures

import "strings"

// SupportsModifiedEnter returns true for terminals that can distinguish
// Shift+Enter from Enter even when they do not report Kitty keyboard flags.
func SupportsModifiedEnter(getenv func(string) string) bool {
if getenv == nil {
return false
}

termProgram := strings.ToLower(getenv("TERM_PROGRAM"))
term := strings.ToLower(getenv("TERM"))

return termProgram == "wezterm" ||
getenv("WEZTERM_PANE") != "" ||
getenv("WEZTERM_UNIX_SOCKET") != "" ||
strings.Contains(term, "wezterm")
}
35 changes: 35 additions & 0 deletions pkg/tui/internal/termfeatures/keyboard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package termfeatures

import "testing"

func TestSupportsModifiedEnter(t *testing.T) {
t.Parallel()

tests := []struct {
name string
env map[string]string
want bool
}{
{name: "wezterm term program", env: map[string]string{"TERM_PROGRAM": "WezTerm"}, want: true},
{name: "wezterm pane", env: map[string]string{"WEZTERM_PANE": "1"}, want: true},
{name: "wezterm socket", env: map[string]string{"WEZTERM_UNIX_SOCKET": "/tmp/wezterm.sock"}, want: true},
{name: "wezterm term", env: map[string]string{"TERM": "wezterm"}, want: true},
{name: "other terminal", env: map[string]string{"TERM_PROGRAM": "Apple_Terminal", "TERM": "xterm-256color"}, want: false},
{name: "nil getenv", env: nil, want: false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

var getenv func(string) string
if tt.env != nil {
getenv = func(key string) string { return tt.env[key] }
}

if got := SupportsModifiedEnter(getenv); got != tt.want {
t.Fatalf("SupportsModifiedEnter() = %v, want %v", got, tt.want)
}
})
}
}
49 changes: 26 additions & 23 deletions pkg/tui/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/docker/docker-agent/pkg/tui/core"
"github.com/docker/docker-agent/pkg/tui/dialog"
"github.com/docker/docker-agent/pkg/tui/internal/editorname"
"github.com/docker/docker-agent/pkg/tui/internal/termfeatures"
"github.com/docker/docker-agent/pkg/tui/messages"
"github.com/docker/docker-agent/pkg/tui/page/chat"
"github.com/docker/docker-agent/pkg/tui/service"
Expand Down Expand Up @@ -287,28 +288,29 @@ func New(ctx context.Context, spawner SessionSpawner, initialApp *app.App, initi
buildCommandCategories: func(ctx context.Context, _ tea.Model) []commands.Category {
return commands.BuildCommandCategories(ctx, initialApp)
},
supervisor: sv,
tabBar: tb,
tuiStore: ts,
chatPages: map[string]chat.Page{},
editors: map[string]editor.Editor{},
sessionStates: map[string]*service.SessionState{sessID: initialSessionState},
application: initialApp,
sessionState: initialSessionState,
history: historyStore,
pendingRestores: make(map[string]string),
pendingSidebarCollapsed: make(map[string]bool),
stashedDialogs: make(map[string]stashedDialog),
notification: notification.New(),
dialogMgr: dialog.New(),
completions: completion.New(),
transcriber: transcribe.New(os.Getenv("OPENAI_API_KEY")),
workingSpinner: spinner.New(spinner.ModeSpinnerOnly, styles.SpinnerDotsHighlightStyle),
focusedPanel: PanelEditor,
editorLines: 3,
dockerDesktop: os.Getenv("TERM_PROGRAM") == "docker_desktop",
appName: "docker agent",
appVersion: version.Version,
supervisor: sv,
tabBar: tb,
tuiStore: ts,
chatPages: map[string]chat.Page{},
editors: map[string]editor.Editor{},
sessionStates: map[string]*service.SessionState{sessID: initialSessionState},
application: initialApp,
sessionState: initialSessionState,
history: historyStore,
pendingRestores: make(map[string]string),
pendingSidebarCollapsed: make(map[string]bool),
stashedDialogs: make(map[string]stashedDialog),
notification: notification.New(),
dialogMgr: dialog.New(),
completions: completion.New(),
transcriber: transcribe.New(os.Getenv("OPENAI_API_KEY")),
workingSpinner: spinner.New(spinner.ModeSpinnerOnly, styles.SpinnerDotsHighlightStyle),
focusedPanel: PanelEditor,
editorLines: 3,
keyboardEnhancementsSupported: termfeatures.SupportsModifiedEnter(os.Getenv),
dockerDesktop: os.Getenv("TERM_PROGRAM") == "docker_desktop",
appName: "docker agent",
appVersion: version.Version,
}

// Apply options
Expand Down Expand Up @@ -651,7 +653,8 @@ func (m *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case tea.KeyboardEnhancementsMsg:
m.keyboardEnhancements = &msg
m.keyboardEnhancementsSupported = msg.Flags != 0
m.keyboardEnhancementsSupported = msg.Flags != 0 || termfeatures.SupportsModifiedEnter(os.Getenv)
m.statusBar.InvalidateCache()
return m, tea.Batch(m.updateChatCmd(msg), m.updateEditorCmd(msg))

// --- Keyboard input ---
Expand Down
23 changes: 23 additions & 0 deletions pkg/tui/tui_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,31 @@ import (
"testing"

tea "charm.land/bubbletea/v2"

"github.com/docker/docker-agent/pkg/tui/components/statusbar"
"github.com/docker/docker-agent/pkg/tui/components/tabbar"
)

func TestKeyboardEnhancementsInvalidateStatusBarHelp(t *testing.T) {
m, _ := newTestModel()
m.focusedPanel = PanelEditor
m.tabBar = tabbar.New(0)
m.statusBar = statusbar.New(m)
m.statusBar.SetWidth(400)

before := m.statusBar.View()
if !strings.Contains(before, "Ctrl+j") {
t.Fatalf("status bar before keyboard enhancements = %q, want Ctrl+j newline help", before)
}

_, _ = m.Update(tea.KeyboardEnhancementsMsg{Flags: 1})

after := m.statusBar.View()
if !strings.Contains(after, "Shift+Enter") {
t.Fatalf("status bar after keyboard enhancements = %q, want Shift+Enter newline help", after)
}
}

func TestParseCtrlNumberKey(t *testing.T) {
t.Parallel()

Expand Down
Loading