terminal: Fix IME position when cursor is hidden (#46592)
Closes https://github.com/zed-industries/zed/issues/45553 ## Summary When using applications that hide the terminal cursor (e.g., via ANSI escape sequence `\e[?25l`), the IME candidate window appeared at the bottom-left of the screen, and the composition text was not displayed at all. This affects tools like Claude Code that hide the cursor during operation. The root cause was that cursor position calculation was skipped when the cursor shape was `Hidden`. This fix separates cursor visibility from IME position calculation by always computing cursor bounds for IME positioning. ## Movies ### Zed 0.218.6 https://github.com/user-attachments/assets/a66d569a-178d-4bca-a577-fcc65e9fd2c4 ### With this fix https://github.com/user-attachments/assets/456323d1-6c18-45ea-88e8-fb5375c00f98 Release Notes: - Fixed IME candidate window and composition text position in terminal when cursor is hidden
This commit is contained in:
1 parent
fdc76ffb18
commit
8f166afe79
1 file changed
+48
-44
@@ -43,6 +43,7 @@ pub struct LayoutState {
|
||||
rects: Vec<LayoutRect>,
|
||||
relative_highlighted_ranges: Vec<(RangeInclusive<AlacPoint>, Hsla)>,
|
||||
cursor: Option<CursorLayout>,
|
||||
ime_cursor_bounds: Option<Bounds<Pixels>>,
|
||||
background_color: Hsla,
|
||||
dimensions: TerminalBounds,
|
||||
mode: TermMode,
|
||||
@@ -1130,49 +1131,54 @@ impl Element for TerminalElement {
|
||||
|
||||
// Layout cursor. Rectangle is used for IME, so we should lay it out even
|
||||
// if we don't end up showing it.
|
||||
let cursor_point = DisplayCursor::from(cursor.point, display_offset);
|
||||
let cursor_text = {
|
||||
let str_trxt = cursor_char.to_string();
|
||||
let len = str_trxt.len();
|
||||
window.text_system().shape_line(
|
||||
str_trxt.into(),
|
||||
text_style.font_size.to_pixels(window.rem_size()),
|
||||
&[TextRun {
|
||||
len,
|
||||
font: text_style.font(),
|
||||
color: theme.colors().terminal_ansi_background,
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let ime_cursor_bounds =
|
||||
TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
|
||||
|(cursor_position, block_width)| Bounds {
|
||||
origin: cursor_position,
|
||||
size: size(block_width, dimensions.line_height),
|
||||
},
|
||||
);
|
||||
|
||||
let cursor = if let AlacCursorShape::Hidden = cursor.shape {
|
||||
None
|
||||
} else {
|
||||
let cursor_point = DisplayCursor::from(cursor.point, display_offset);
|
||||
let cursor_text = {
|
||||
let str_trxt = cursor_char.to_string();
|
||||
let len = str_trxt.len();
|
||||
window.text_system().shape_line(
|
||||
str_trxt.into(),
|
||||
text_style.font_size.to_pixels(window.rem_size()),
|
||||
&[TextRun {
|
||||
len,
|
||||
font: text_style.font(),
|
||||
color: theme.colors().terminal_ansi_background,
|
||||
..Default::default()
|
||||
}],
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
let focused = self.focused;
|
||||
TerminalElement::shape_cursor(cursor_point, dimensions, &cursor_text).map(
|
||||
move |(cursor_position, block_width)| {
|
||||
let (shape, text) = match cursor.shape {
|
||||
AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
|
||||
AlacCursorShape::Underline => (CursorShape::Underline, None),
|
||||
AlacCursorShape::Beam => (CursorShape::Bar, None),
|
||||
AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
|
||||
//This case is handled in the if wrapping the whole cursor layout
|
||||
AlacCursorShape::Hidden => unreachable!(),
|
||||
};
|
||||
ime_cursor_bounds.map(move |bounds| {
|
||||
let (shape, text) = match cursor.shape {
|
||||
AlacCursorShape::Block if !focused => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Block => (CursorShape::Block, Some(cursor_text)),
|
||||
AlacCursorShape::Underline => (CursorShape::Underline, None),
|
||||
AlacCursorShape::Beam => (CursorShape::Bar, None),
|
||||
AlacCursorShape::HollowBlock => (CursorShape::Hollow, None),
|
||||
AlacCursorShape::Hidden => unreachable!(),
|
||||
};
|
||||
|
||||
CursorLayout::new(
|
||||
cursor_position,
|
||||
block_width,
|
||||
dimensions.line_height,
|
||||
theme.players().local().cursor,
|
||||
shape,
|
||||
text,
|
||||
)
|
||||
},
|
||||
)
|
||||
CursorLayout::new(
|
||||
bounds.origin,
|
||||
bounds.size.width,
|
||||
bounds.size.height,
|
||||
theme.players().local().cursor,
|
||||
shape,
|
||||
text,
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
let block_below_cursor_element = if let Some(block) = &self.block_below_cursor {
|
||||
@@ -1211,6 +1217,7 @@ impl Element for TerminalElement {
|
||||
hitbox,
|
||||
batched_text_runs,
|
||||
cursor,
|
||||
ime_cursor_bounds,
|
||||
background_color,
|
||||
dimensions,
|
||||
rects,
|
||||
@@ -1253,10 +1260,7 @@ impl Element for TerminalElement {
|
||||
let terminal_input_handler = TerminalInputHandler {
|
||||
terminal: self.terminal.clone(),
|
||||
terminal_view: self.terminal_view.clone(),
|
||||
cursor_bounds: layout
|
||||
.cursor
|
||||
.as_ref()
|
||||
.map(|cursor| cursor.bounding_rect(origin)),
|
||||
cursor_bounds: layout.ime_cursor_bounds.map(|bounds| bounds + origin),
|
||||
workspace: self.workspace.clone(),
|
||||
};
|
||||
|
||||
@@ -1336,8 +1340,8 @@ impl Element for TerminalElement {
|
||||
|
||||
if let Some(text_to_mark) = &marked_text_cloned
|
||||
&& !text_to_mark.is_empty()
|
||||
&& let Some(cursor_layout) = &original_cursor {
|
||||
let ime_position = cursor_layout.bounding_rect(origin).origin;
|
||||
&& let Some(ime_bounds) = layout.ime_cursor_bounds {
|
||||
let ime_position = (ime_bounds + origin).origin;
|
||||
let mut ime_style = layout.base_text_style.clone();
|
||||
ime_style.underline = Some(UnderlineStyle {
|
||||
color: Some(ime_style.color),
|
||||
|
||||
Reference in new issue
Block a user