Back
gh

Shift + Enter Executes Command Instead of Creating a Newline in Integrated Terminal · openai codex · Discussion #3024

Shift + Enter Executes Command Instead of Creating a Newline in Integrated Terminal

by openai github.com 1,028 words
View original

Try adding this keybinding to config.keys so Shift+Enter sends a newline.

{
  key = "Enter",
  mods = "SHIFT",
  action = wezterm.action.SendString "\x0a",
}

I haven’t run into any conflicts or issues with my setup. 🤷

Agreed on it being a wild design decision. But if you are on WSL, here is a workaround that works for me. (And there are probably similar techniques for other terminals).

  1. Edit the terminal settings.json
  1. Add a binding under “keybindings:”
{
            "id": "User.sendLF",
            "keys": "shift+enter"
        },
  1. Add an action for that keybinding under “actions”:
{
            "command": 
            {
                "action": "sendInput",
                "input": "\u000A"
            },
            "id": "User.sendLF"
        }

So a minimal settings.json could look like:

{
    "$help": "https://aka.ms/terminal-documentation",
    "$schema": "https://aka.ms/terminal-profiles-schema-preview",
    "actions": 
    [
        {
            "command": 
            {
                "action": "sendInput",
                "input": "\u000A"
            },
            "id": "User.sendLF"
        }
    ],
    "keybindings": 
    [
        {
            "id": "User.sendLF",
            "keys": "shift+enter"
        },
    ]
}

0 replies

For anyone digging into the root cause, this behavior is tracked upstream in Crossterm here:
crossterm-rs/crossterm#685

Just for context, Crossterm is a small, maintainer-driven project and I also help maintain it in my own time. That issue already captures the problem space well, so if you head over there, what is most helpful is concrete data (for example what key events your terminal actually sends), rather than +1s or duplicate reports.

Keeping the signal high there makes it much more likely we can actually improve things for everyone.

The important bit: Ctrl+J isn’t an arbitrary shortcut. It corresponds to the ASCII control code for newline (LF). Many terminals do not reliably produce a distinct “Shift+Enter” (or “Ctrl+Enter”) key event, and in some environments those combinations collapse into the same underlying newline signal.

That leaves a few practical paths forward:

  1. Improve Crossterm so it can preserve more of these key combinations across terminals (where the terminal actually sends something distinguishable).
  2. Capture real behavior across terminals + multiplexers and ship sensible defaults per environment (terminal + tmux/zellij can matter a lot here).
  3. Make it configurable so users can pick a newline insertion binding that doesn’t conflict with their setup (tmux pane navigation via Ctrl+J is a common conflict).

Some combination of these is likely the right long-term answer.

We can’t realistically cover every terminal in existence (there’s a long tail), but we do test regularly on the major ones (VS Code, Terminal.app, iTerm2, Ghostty; and when possible Windows Terminal, plus sometimes Kitty/Alacritty/WezTerm).

If you want to see exactly what your terminal is sending for Shift+Enter / Ctrl+Enter, Crossterm includes a tiny event viewer:

gh repo clone crossterm-rs/crossterm
cd crossterm
cargo run --example event-read

If folks are willing, it would be extremely helpful to build a community list of these outputs including terminal + OS + whether you’re in tmux/zellij. That data is what lets us decide whether Shift+Enter can be supported reliably, whether we need per-terminal defaults, and what configuration knobs make sense.

I’ll start by posting Ghostty results below.

Note: this should mostly be around testing what characters shift+enter produces.

0 replies

Ghostty 1.2.3 (Build 12214) on macOS Sequoia

gh repo clone crossterm-rs/crossterm; cd crossterm; cargo run --example event-read

Event: Key(KeyEvent { code: Modifier(LeftShift), modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Modifier(LeftShift), modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Esc, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

1 reply

Ghostty 1.2.3 (Build 12214) on macOS Sequoia running within tmux doesn’t seem to register the key modifier for me:

% cargo run --example event-read
    Finished \`dev\` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running \`target/debug/examples/event-read\`
Blocking read()
 - Keyboard, mouse, focus and terminal resize events enabled
 - Hit "c" to print current cursor position
 - Use Esc to quit

Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

Iterm2 Build 3.6.7beta2 on macOS Sequoia

Event: Key(KeyEvent { code: Modifier(LeftShift), modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Modifier(LeftShift), modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Esc, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

3 replies

That’s a really odd result. I’d be curious about your settings that you’re seeing both shift+enter and ctrl+j for the one keystroke there. Can’t repro on the stable version of iTerm2.

Event: Key(KeyEvent { code: Modifier(RightShift), modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Press, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Enter, modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Modifier(RightShift), modifiers: KeyModifiers(SHIFT), kind: Release, state: KeyEventState(0x0) })
Event: Key(KeyEvent { code: Esc, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

macOS + Tabby: crossterm keydump shows Shift+Enter is identical to Enter:

KeyEvent { code: Enter, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }
KeyEvent { code: Enter, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) }

Workaround: Karabiner‑Elements remap Shift+Enter → LF (Ctrl+J). Terminal‑agnostic; example below scopes it to Tabby via bundle id.

Create rule file: ~/.config/karabiner/assets/complex_modifications/tabby-shift-enter.json

{
  "title": "Tabby: Shift+Enter -> Ctrl+J",
  "rules": [
    {
      "description": "Tabby: Shift+Enter sends LF (Ctrl+J)",
      "manipulators": [
        {
          "type": "basic",
          "from": {
            "key_code": "return_or_enter",
            "modifiers": { "mandatory": ["shift"], "optional": ["any"] }
          },
          "to": [{ "key_code": "j", "modifiers": ["control"] }],
          "conditions": [
            {
              "type": "frontmost_application_if",
              "bundle_identifiers": ["^org\\.tabby$"]
            }
          ]
        }
      ]
    }
  ]
}

Enable: Karabiner‑Elements → Complex Modifications → Add rule → “Tabby: Shift+Enter -> Ctrl+J”.
To use another terminal, change the bundle id (or remove the condition for global).

0 replies

🙏🏻 Nicely figured out. Worked for me on MacOS/Wezterm/tmux:

-- wezterm config
  {
    key = "Enter",
    mods = "SHIFT",
    action = action.SendString("\x1b\r"),
  }