[BUG] Shift+Enter and Ctrl+J do not insert newlines in chat input on code-server / webview — cursor desyncs from visible text
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
- Open Claude Code in code-server (or any web-based VS Code environment)
- Type "hello" in the chat input
- Press Shift+Enter
- Type "world"
- Expected: "hello" and "world" on separate lines
- 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.js—qetcomponent (chat input)onKeyDownhandler (Ri): missing Shift+Enter interceptiononInputhandler (so): usestextContentinstead ofinnerText- Submit function (
$) and insert handlers (kn,Oa,ks): same issue webview/index.css—.messageInput_cKsPxg: missingwhite-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 handlerasync function $()— submit handlerkn,Oacallbacks — insert handlersks— 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.