Multi-line Editor Preserves CRLF Carriage Returns From Windows Clipboard
Summary
gpui-component multi-line EditorState preserves \r from Windows CRLF clipboard text. This can make the first N-1 pasted lines render as blank or disappear visually, while the underlying text is still present.
The final line usually renders normally because it has no trailing \r.
Environment
- Windows
- Clipboard source: VS Code
-
gpui = { git = "https://github.com/zed-industries/zed" }
gpui-component = { git = "https://github.com/longbridge/gpui-component" }
Reproduction
-
Create a multi-line gpui_component::input::Editor.
-
Paste the following text from VS Code:
use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Range;
use std::rc::Rc;
-
Select and copy the first line.
-
Move the caret to the end of that first line.
-
Paste three times with Ctrl+V.
Actual Behavior
- The first line, and generally all pasted lines except the final logical line, can become visually blank.
- Typing on those lines also appears blank.
Ctrl+A, then Ctrl+C, then pasting into another application shows the text is still present.
- The copied text reveals that the content was inserted as separate lines.
- If five lines are initially pasted, the first four can fail while the last line renders normally.
- Copying through Notepad++ avoids the issue, likely because it converts clipboard line endings to LF.
Expected Behavior
All pasted lines should remain visible and editable, independent of whether the clipboard source uses LF or CRLF.
Root Cause
Windows clipboard content from VS Code uses CRLF:
line1\r\nline2\r\nline3\r\nline4
The multi-line input path in gpui-component preserves \r.
The relevant flow is:
Paste
-> replace_text_in_range_silent
-> replace_text_in_range
-> normalize_input
In crates/base/src/input/base/state.rs, normalize_input currently removes \r and \n only for single-line inputs. Multi-line editors retain \r.
As a result, \r is treated as a normal shaped character at the end of every line except the last. This produces the predictable "first N-1 lines fail, final line works" behavior.
Proposed Fix
Normalize all multi-line input to LF before inserting text:
fn normalize_input<'a>(&self, new_text: &'a str) -> Cow<'a, str> {
let normalized = if matches!(self.mask_pattern, MaskPattern::Number { .. }) {
normalize_number_input(new_text)
} else {
Cow::Borrowed(new_text)
};
if self.is_single_line() && normalized.contains(['\n', '\r']) {
Cow::Owned(normalized.replace(['\n', '\r'], ""))
} else if self.is_multi_line() && normalized.contains('\r') {
Cow::Owned(normalized.replace("\r\n", "\n").replace('\r', "\n"))
} else {
normalized
}
}
This preserves the internal LF-only line model and prevents \r from reaching shaping, layout, cursor hit testing, and display-map code.
Application Workaround
As a temporary workaround, the application can override the component paste action after gpui_component::init(cx):
gpui_component::init(cx);
cx.bind_keys([KeyBinding::new(
"ctrl-v",
PasteNormalized,
Some("Input"),
)]);
The custom action reads clipboard text, converts CRLF/CR to LF, then calls EditorState::replace.
The order is important: registering before gpui_component::init(cx) does not work because component initialization installs its default key bindings afterward.
Multi-line Editor Preserves CRLF Carriage Returns From Windows Clipboard
Summary
gpui-componentmulti-lineEditorStatepreserves\rfrom Windows CRLF clipboard text. This can make the first N-1 pasted lines render as blank or disappear visually, while the underlying text is still present.The final line usually renders normally because it has no trailing
\r.Environment
Reproduction
Create a multi-line
gpui_component::input::Editor.Paste the following text from VS Code:
Select and copy the first line.
Move the caret to the end of that first line.
Paste three times with
Ctrl+V.Actual Behavior
Ctrl+A, thenCtrl+C, then pasting into another application shows the text is still present.Expected Behavior
All pasted lines should remain visible and editable, independent of whether the clipboard source uses LF or CRLF.
Root Cause
Windows clipboard content from VS Code uses CRLF:
The multi-line input path in
gpui-componentpreserves\r.The relevant flow is:
In
crates/base/src/input/base/state.rs,normalize_inputcurrently removes\rand\nonly for single-line inputs. Multi-line editors retain\r.As a result,
\ris treated as a normal shaped character at the end of every line except the last. This produces the predictable "first N-1 lines fail, final line works" behavior.Proposed Fix
Normalize all multi-line input to LF before inserting text:
This preserves the internal LF-only line model and prevents
\rfrom reaching shaping, layout, cursor hit testing, and display-map code.Application Workaround
As a temporary workaround, the application can override the component paste action after
gpui_component::init(cx):The custom action reads clipboard text, converts CRLF/CR to LF, then calls
EditorState::replace.The order is important: registering before
gpui_component::init(cx)does not work because component initialization installs its default key bindings afterward.