diff --git a/pkg/tui/components/editor/editor.go b/pkg/tui/components/editor/editor.go index a4e1fbcf5..a1c306979 100644 --- a/pkg/tui/components/editor/editor.go +++ b/pkg/tui/components/editor/editor.go @@ -771,6 +771,16 @@ func (e *editor) Update(msg tea.Msg) (layout.Model, tea.Cmd) { return e, nil } + // On macOS, terminals like Terminal.app don't support the Kitty keyboard + // protocol, so Shift+Enter sends the same byte as Enter. Use the macOS + // CoreGraphics API to check if Shift is physically held, and treat it + // as a newline insertion. + if msg.String() == "enter" && termfeatures.IsShiftPressed() { + e.textarea.InsertString("\n") + e.refreshSuggestion() + return e, nil + } + // Let textarea process the key - it handles newlines via InsertNewline binding prev := e.textarea.Value() e.textarea, _ = e.textarea.Update(msg) diff --git a/pkg/tui/internal/termfeatures/keyboard.go b/pkg/tui/internal/termfeatures/keyboard.go index dc0d64efb..f59a60e4f 100644 --- a/pkg/tui/internal/termfeatures/keyboard.go +++ b/pkg/tui/internal/termfeatures/keyboard.go @@ -1,10 +1,19 @@ package termfeatures -import "strings" +import ( + "runtime" + "strings" +) // SupportsModifiedEnter returns true for terminals that can distinguish // Shift+Enter from Enter even when they do not report Kitty keyboard flags. +// On macOS, we use the CoreGraphics API to detect modifier key state at the +// system level, so all terminals effectively support this. func SupportsModifiedEnter(getenv func(string) string) bool { + if runtime.GOOS == "darwin" { + return true + } + if getenv == nil { return false } diff --git a/pkg/tui/internal/termfeatures/keyboard_test.go b/pkg/tui/internal/termfeatures/keyboard_test.go index e2bed09e5..b82e9e426 100644 --- a/pkg/tui/internal/termfeatures/keyboard_test.go +++ b/pkg/tui/internal/termfeatures/keyboard_test.go @@ -1,6 +1,9 @@ package termfeatures -import "testing" +import ( + "runtime" + "testing" +) func TestSupportsModifiedEnter(t *testing.T) { t.Parallel() @@ -14,8 +17,8 @@ func TestSupportsModifiedEnter(t *testing.T) { {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}, + {name: "other terminal", env: map[string]string{"TERM_PROGRAM": "Apple_Terminal", "TERM": "xterm-256color"}, want: runtime.GOOS == "darwin"}, + {name: "nil getenv", env: nil, want: runtime.GOOS == "darwin"}, } for _, tt := range tests { diff --git a/pkg/tui/internal/termfeatures/modifiers_darwin.go b/pkg/tui/internal/termfeatures/modifiers_darwin.go new file mode 100644 index 000000000..f8c23bcf1 --- /dev/null +++ b/pkg/tui/internal/termfeatures/modifiers_darwin.go @@ -0,0 +1,23 @@ +//go:build darwin + +package termfeatures + +/* +#cgo LDFLAGS: -framework CoreGraphics +#include + +static int isShiftPressed() { + CGEventFlags flags = CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState); + return (flags & kCGEventFlagMaskShift) != 0; +} +*/ +import "C" + +// IsShiftPressed queries the macOS CoreGraphics event system to determine +// whether the Shift key is currently held down. This allows us to distinguish +// Shift+Enter from Enter in terminals that don't support the Kitty keyboard +// protocol (like macOS Terminal.app), since those terminals send the same byte +// for both key combinations. +func IsShiftPressed() bool { + return C.isShiftPressed() != 0 +} diff --git a/pkg/tui/internal/termfeatures/modifiers_other.go b/pkg/tui/internal/termfeatures/modifiers_other.go new file mode 100644 index 000000000..fe8b029bd --- /dev/null +++ b/pkg/tui/internal/termfeatures/modifiers_other.go @@ -0,0 +1,9 @@ +//go:build !darwin + +package termfeatures + +// IsShiftPressed returns false on non-macOS platforms. The CoreGraphics-based +// modifier detection is only available on macOS. +func IsShiftPressed() bool { + return false +}