[BUG] Shift+Enter and Ctrl+J do not insert newlines in chat input on code-server / webview — cursor desyncs from visible text

Resolved 💬 0 comments Opened Jun 19, 2026 by rickwwld Closed Jun 25, 2026

Description

In the VS Code extension webview (particularly when running inside code-server), pressing Shift+Enter or Ctrl+J in the chat input does not correctly insert a newline. The cursor moves to the next line but the typed text continues on the first line, causing the cursor position to be out of sync with the visible text.

This was reproduced on code-server (Linux, Chromium 148) but the root cause affects any browser where contentEditable="plaintext-only" is not fully supported.

Steps to Reproduce

  1. Open Claude Code in code-server (or any web-based VS Code environment)
  2. Type "hello" in the chat input
  3. Press Shift+Enter
  4. Type "world"
  5. Expected: "hello" and "world" on separate lines
  6. Actual: Text displays as "helloworld" on one line, but cursor is on the second line

Root Cause Analysis

1. contentEditable="plaintext-only" fallback behavior

The chat input uses <div contentEditable="plaintext-only" ...> (messageInput_cKsPxg). When the browser does not support plaintext-only (it was added in Chrome 110+), it falls back to contentEditable="true". In that mode, Shift+Enter inserts <br> or <div> HTML elements instead of \n characters.

2. textContent vs innerText

The onInput handler (so function in the qet component) and other text-reading functions ($, kn, Oa, ks) read from the contentEditable using textContent:

function so(){let q=J.current?.textContent||"";...}
async function $(q){let Te=J.current?.textContent?.trim()||"";...}

textContent does not convert <br> or <div> elements to \n. So when the browser inserts <br> on Shift+Enter, the React state loses the line break. Meanwhile innerText correctly includes \n for these elements.

3. Transparent-input + mirror pattern

The input uses color: #0000 (transparent text) with a mentionMirror div overlaid on top. The mirror renders the React state (via Rr memo). When the state lacks newlines but the contentEditable DOM has them (from <br> elements), cursor position and visible text desync.

4. Missing Ctrl+J handling

There is no keydown handler for Ctrl+J (ASCII LF), which is a common alternative to Shift+Enter for inserting newlines. The CLI version supports this.

Affected Code

  • webview/index.jsqet component (chat input)
  • onKeyDown handler (Ri): missing Shift+Enter interception
  • onInput handler (so): uses textContent instead of innerText
  • Submit function ($) and insert handlers (kn, Oa, ks): same issue
  • webview/index.css.messageInput_cKsPxg: missing white-space: pre-wrap

Suggested Fix

1. Intercept Shift+Enter and Ctrl+J in keydown handler

// Add before the existing Enter-without-Shift handler:
if(q.key==="Enter"&&q.shiftKey||q.key==="j"&&q.ctrlKey&&!q.shiftKey&&!q.altKey&&!q.metaKey){
  q.preventDefault();
  document.execCommand("insertText",!1,"\n");
  return;
}

2. Use innerText instead of textContent when reading from contentEditable

Change all J.current?.textContent reads to J.current?.innerText:

  • function so() — onInput handler
  • async function $() — submit handler
  • kn, Oa callbacks — insert handlers
  • ks — speech-to-text start

3. Add white-space: pre-wrap to .messageInput CSS

messageInput_cKsPxg {
  outline: none;
  white-space: pre-wrap; /* ensure \n renders as line breaks */
  overflow-y: auto;
  ...
}

Environment

  • Extension version: 2.1.183 (code-server), 2.1.181 (VS Code)
  • Platform: Linux (Raspberry Pi OS / Debian Bookworm)
  • Browser: Chromium 148.0.7778.167
  • code-server: latest

Related Issues

  • #1262 — Shift+Enter in WSL
  • #22719 — Shift+Enter in iTerm2/macOS (native)
  • #63847 — Arrow key navigation in multi-line text

Note

This issue has been reproduced and a workaround fix has been applied locally. The fix script and detailed analysis are available if needed. Happy to provide a PR if the team is open to it.

View original on GitHub ↗