mirror of
https://github.com/alacritty/alacritty.git
synced 2025-09-04 22:43:53 -04:00
Implement multi-char cursor highlight
Use `end` of the cursor to draw a `HollowBlock` from `start` to `end`. When cursor covers only a single character, use `Beam` cursor instead of `HollowBlock`. Fixes #8238. Fixes #7849.
This commit is contained in:
parent
6b3a85606b
commit
b56a0e86b7
5 changed files with 49 additions and 35 deletions
|
@ -29,6 +29,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its
|
||||||
to the new `general` section
|
to the new `general` section
|
||||||
- Moved config option `shell` to `terminal.shell`
|
- Moved config option `shell` to `terminal.shell`
|
||||||
- `ctrl+shift+u` binding to open links to `ctrl+shift+o` to avoid collisions with IMEs
|
- `ctrl+shift+u` binding to open links to `ctrl+shift+o` to avoid collisions with IMEs
|
||||||
|
- Use `Beam` cursor for single char cursor inside the IME preview
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
@ -48,6 +49,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its
|
||||||
- Windows app icon now displays properly in old alt+tab on Windows
|
- Windows app icon now displays properly in old alt+tab on Windows
|
||||||
- Alacritty not being properly activated with startup notify
|
- Alacritty not being properly activated with startup notify
|
||||||
- Invalid URL highlights after terminal scrolling
|
- Invalid URL highlights after terminal scrolling
|
||||||
|
- Hollow block cursor not spanning multiple chars being edited inside the IME preview
|
||||||
|
|
||||||
## 0.13.2
|
## 0.13.2
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::num::NonZeroU32;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::{cmp, mem};
|
use std::{cmp, mem};
|
||||||
|
|
||||||
|
@ -134,8 +135,13 @@ impl<'a> RenderableContent<'a> {
|
||||||
text_color = self.config.colors.primary.background;
|
text_color = self.config.colors.primary.background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let width = if cell.flags.contains(Flags::WIDE_CHAR) {
|
||||||
|
NonZeroU32::new(2).unwrap()
|
||||||
|
} else {
|
||||||
|
NonZeroU32::new(1).unwrap()
|
||||||
|
};
|
||||||
RenderableCursor {
|
RenderableCursor {
|
||||||
is_wide: cell.flags.contains(Flags::WIDE_CHAR),
|
width,
|
||||||
shape: self.cursor_shape,
|
shape: self.cursor_shape,
|
||||||
point: self.cursor_point,
|
point: self.cursor_point,
|
||||||
cursor_color,
|
cursor_color,
|
||||||
|
@ -396,7 +402,7 @@ pub struct RenderableCursor {
|
||||||
shape: CursorShape,
|
shape: CursorShape,
|
||||||
cursor_color: Rgb,
|
cursor_color: Rgb,
|
||||||
text_color: Rgb,
|
text_color: Rgb,
|
||||||
is_wide: bool,
|
width: NonZeroU32,
|
||||||
point: Point<usize>,
|
point: Point<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,15 +411,20 @@ impl RenderableCursor {
|
||||||
let shape = CursorShape::Hidden;
|
let shape = CursorShape::Hidden;
|
||||||
let cursor_color = Rgb::default();
|
let cursor_color = Rgb::default();
|
||||||
let text_color = Rgb::default();
|
let text_color = Rgb::default();
|
||||||
let is_wide = false;
|
let width = NonZeroU32::new(1).unwrap();
|
||||||
let point = Point::default();
|
let point = Point::default();
|
||||||
Self { shape, cursor_color, text_color, is_wide, point }
|
Self { shape, cursor_color, text_color, width, point }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderableCursor {
|
impl RenderableCursor {
|
||||||
pub fn new(point: Point<usize>, shape: CursorShape, cursor_color: Rgb, is_wide: bool) -> Self {
|
pub fn new(
|
||||||
Self { shape, cursor_color, text_color: cursor_color, is_wide, point }
|
point: Point<usize>,
|
||||||
|
shape: CursorShape,
|
||||||
|
cursor_color: Rgb,
|
||||||
|
width: NonZeroU32,
|
||||||
|
) -> Self {
|
||||||
|
Self { shape, cursor_color, text_color: cursor_color, width, point }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color(&self) -> Rgb {
|
pub fn color(&self) -> Rgb {
|
||||||
|
@ -424,8 +435,8 @@ impl RenderableCursor {
|
||||||
self.shape
|
self.shape
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_wide(&self) -> bool {
|
pub fn width(&self) -> NonZeroU32 {
|
||||||
self.is_wide
|
self.width
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn point(&self) -> Point<usize> {
|
pub fn point(&self) -> Point<usize> {
|
||||||
|
|
|
@ -24,9 +24,7 @@ impl IntoRects for RenderableCursor {
|
||||||
|
|
||||||
let thickness = (thickness * width).round().max(1.);
|
let thickness = (thickness * width).round().max(1.);
|
||||||
|
|
||||||
if self.is_wide() {
|
width *= self.width().get() as f32;
|
||||||
width *= 2.;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.shape() {
|
match self.shape() {
|
||||||
CursorShape::Beam => beam(x, y, height, thickness, self.color()),
|
CursorShape::Beam => beam(x, y, height, thickness, self.color()),
|
||||||
|
|
|
@ -874,7 +874,9 @@ impl Display {
|
||||||
if self.ime.preedit().is_none() {
|
if self.ime.preedit().is_none() {
|
||||||
let fg = config.colors.footer_bar_foreground();
|
let fg = config.colors.footer_bar_foreground();
|
||||||
let shape = CursorShape::Underline;
|
let shape = CursorShape::Underline;
|
||||||
let cursor = RenderableCursor::new(Point::new(line, column), shape, fg, false);
|
let cursor_width = NonZeroU32::new(1).unwrap();
|
||||||
|
let cursor =
|
||||||
|
RenderableCursor::new(Point::new(line, column), shape, fg, cursor_width);
|
||||||
rects.extend(cursor.rects(&size_info, config.cursor.thickness()));
|
rects.extend(cursor.rects(&size_info, config.cursor.thickness()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1081,8 +1083,8 @@ impl Display {
|
||||||
|
|
||||||
// Get the visible preedit.
|
// Get the visible preedit.
|
||||||
let visible_text: String = match (preedit.cursor_byte_offset, preedit.cursor_end_offset) {
|
let visible_text: String = match (preedit.cursor_byte_offset, preedit.cursor_end_offset) {
|
||||||
(Some(byte_offset), Some(end_offset)) if end_offset > num_cols => StrShortener::new(
|
(Some(byte_offset), Some(end_offset)) if end_offset.0 > num_cols => StrShortener::new(
|
||||||
&preedit.text[byte_offset..],
|
&preedit.text[byte_offset.0..],
|
||||||
num_cols,
|
num_cols,
|
||||||
ShortenDirection::Right,
|
ShortenDirection::Right,
|
||||||
Some(SHORTENER),
|
Some(SHORTENER),
|
||||||
|
@ -1125,19 +1127,21 @@ impl Display {
|
||||||
rects.extend(underline.rects(Flags::UNDERLINE, &metrics, &self.size_info));
|
rects.extend(underline.rects(Flags::UNDERLINE, &metrics, &self.size_info));
|
||||||
|
|
||||||
let ime_popup_point = match preedit.cursor_end_offset {
|
let ime_popup_point = match preedit.cursor_end_offset {
|
||||||
Some(cursor_end_offset) if cursor_end_offset != 0 => {
|
Some(cursor_end_offset) => {
|
||||||
let is_wide = preedit.text[preedit.cursor_byte_offset.unwrap_or_default()..]
|
// Use hollow block when multiple characters are changed at once.
|
||||||
.chars()
|
let (shape, width) = if let Some(width) =
|
||||||
.next()
|
NonZeroU32::new((cursor_end_offset.0 - cursor_end_offset.1) as u32)
|
||||||
.map(|ch| ch.width() == Some(2))
|
{
|
||||||
.unwrap_or_default();
|
(CursorShape::HollowBlock, width)
|
||||||
|
} else {
|
||||||
|
(CursorShape::Beam, NonZeroU32::new(1).unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
let cursor_column = Column(
|
let cursor_column = Column(
|
||||||
(end.column.0 as isize - cursor_end_offset as isize + 1).max(0) as usize,
|
(end.column.0 as isize - cursor_end_offset.0 as isize + 1).max(0) as usize,
|
||||||
);
|
);
|
||||||
let cursor_point = Point::new(point.line, cursor_column);
|
let cursor_point = Point::new(point.line, cursor_column);
|
||||||
let cursor =
|
let cursor = RenderableCursor::new(cursor_point, shape, fg, width);
|
||||||
RenderableCursor::new(cursor_point, CursorShape::HollowBlock, fg, is_wide);
|
|
||||||
rects.extend(cursor.rects(&self.size_info, config.cursor.thickness()));
|
rects.extend(cursor.rects(&self.size_info, config.cursor.thickness()));
|
||||||
cursor_point
|
cursor_point
|
||||||
},
|
},
|
||||||
|
@ -1436,20 +1440,22 @@ pub struct Preedit {
|
||||||
/// Byte offset for cursor start into the preedit text.
|
/// Byte offset for cursor start into the preedit text.
|
||||||
///
|
///
|
||||||
/// `None` means that the cursor is invisible.
|
/// `None` means that the cursor is invisible.
|
||||||
cursor_byte_offset: Option<usize>,
|
cursor_byte_offset: Option<(usize, usize)>,
|
||||||
|
|
||||||
/// The cursor offset from the end of the preedit in char width.
|
/// The cursor offset from the end of the start of the preedit in char width.
|
||||||
cursor_end_offset: Option<usize>,
|
cursor_end_offset: Option<(usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Preedit {
|
impl Preedit {
|
||||||
pub fn new(text: String, cursor_byte_offset: Option<usize>) -> Self {
|
pub fn new(text: String, cursor_byte_offset: Option<(usize, usize)>) -> Self {
|
||||||
let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset {
|
let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset {
|
||||||
// Convert byte offset into char offset.
|
// Convert byte offset into char offset.
|
||||||
let cursor_end_offset =
|
let start_to_end_offset =
|
||||||
text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1));
|
text[byte_offset.0..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1));
|
||||||
|
let end_to_end_offset =
|
||||||
|
text[byte_offset.1..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1));
|
||||||
|
|
||||||
Some(cursor_end_offset)
|
Some((start_to_end_offset, end_to_end_offset))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
|
@ -1843,11 +1843,8 @@ impl input::Processor<EventProxy, ActionContext<'_, Notifier, EventProxy>> {
|
||||||
self.ctx.update_cursor_blinking();
|
self.ctx.update_cursor_blinking();
|
||||||
},
|
},
|
||||||
Ime::Preedit(text, cursor_offset) => {
|
Ime::Preedit(text, cursor_offset) => {
|
||||||
let preedit = if text.is_empty() {
|
let preedit =
|
||||||
None
|
(!text.is_empty()).then(|| Preedit::new(text, cursor_offset));
|
||||||
} else {
|
|
||||||
Some(Preedit::new(text, cursor_offset.map(|offset| offset.0)))
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.ctx.display.ime.preedit() != preedit.as_ref() {
|
if self.ctx.display.ime.preedit() != preedit.as_ref() {
|
||||||
self.ctx.display.ime.set_preedit(preedit);
|
self.ctx.display.ime.set_preedit(preedit);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue