aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/display
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty/src/display')
-rw-r--r--alacritty/src/display/content.rs5
-rw-r--r--alacritty/src/display/mod.rs251
-rw-r--r--alacritty/src/display/window.rs8
3 files changed, 232 insertions, 32 deletions
diff --git a/alacritty/src/display/content.rs b/alacritty/src/display/content.rs
index 478982bb..c062ba9b 100644
--- a/alacritty/src/display/content.rs
+++ b/alacritty/src/display/content.rs
@@ -51,6 +51,7 @@ impl<'a> RenderableContent<'a> {
let cursor_shape = if terminal_content.cursor.shape == CursorShape::Hidden
|| display.cursor_hidden
|| search_state.regex().is_some()
+ || display.ime.preedit().is_some()
{
CursorShape::Hidden
} else if !term.is_focused && config.terminal_config.cursor.unfocused_hollow {
@@ -394,6 +395,10 @@ impl RenderableCursor {
}
impl RenderableCursor {
+ pub fn new(point: Point<usize>, shape: CursorShape, cursor_color: Rgb, is_wide: bool) -> Self {
+ Self { shape, cursor_color, text_color: cursor_color, is_wide, point }
+ }
+
pub fn color(&self) -> Rgb {
self.cursor_color
}
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index 7bd50049..e48d2b68 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -20,8 +20,9 @@ use serde::{Deserialize, Serialize};
use wayland_client::EventQueue;
use crossfont::{self, Rasterize, Rasterizer};
+use unicode_width::UnicodeWidthChar;
-use alacritty_terminal::ansi::NamedColor;
+use alacritty_terminal::ansi::{CursorShape, NamedColor};
use alacritty_terminal::config::MAX_SCROLLBACK_LINES;
use alacritty_terminal::event::{EventListener, OnResize, WindowSize};
use alacritty_terminal::grid::Dimensions as TermDimensions;
@@ -38,7 +39,7 @@ use crate::config::window::{Dimensions, Identity};
use crate::config::UiConfig;
use crate::display::bell::VisualBell;
use crate::display::color::List;
-use crate::display::content::RenderableContent;
+use crate::display::content::{RenderableContent, RenderableCursor};
use crate::display::cursor::IntoRects;
use crate::display::damage::RenderDamageIterator;
use crate::display::hint::{HintMatch, HintState};
@@ -46,7 +47,7 @@ use crate::display::meter::Meter;
use crate::display::window::Window;
use crate::event::{Mouse, SearchState};
use crate::message_bar::{MessageBuffer, MessageType};
-use crate::renderer::rects::{RenderLines, RenderRect};
+use crate::renderer::rects::{RenderLine, RenderLines, RenderRect};
use crate::renderer::{self, GlyphCache, Renderer};
use crate::string::{ShortenDirection, StrShortener};
@@ -362,6 +363,9 @@ pub struct Display {
/// The renderer update that takes place only once before the actual rendering.
pub pending_renderer_update: Option<RendererUpdate>,
+ /// The ime on the given display.
+ pub ime: Ime,
+
// Mouse point position when highlighting hints.
hint_mouse_point: Option<Point>,
@@ -374,6 +378,77 @@ pub struct Display {
meter: Meter,
}
+/// Input method state.
+#[derive(Debug, Default)]
+pub struct Ime {
+ /// Whether the IME is enabled.
+ enabled: bool,
+
+ /// Current IME preedit.
+ preedit: Option<Preedit>,
+}
+
+impl Ime {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ #[inline]
+ pub fn set_enabled(&mut self, is_enabled: bool) {
+ if is_enabled {
+ self.enabled = is_enabled
+ } else {
+ // Clear state when disabling IME.
+ *self = Default::default();
+ }
+ }
+
+ #[inline]
+ pub fn is_enabled(&self) -> bool {
+ self.enabled
+ }
+
+ #[inline]
+ pub fn set_preedit(&mut self, preedit: Option<Preedit>) {
+ self.preedit = preedit;
+ }
+
+ #[inline]
+ pub fn preedit(&self) -> Option<&Preedit> {
+ self.preedit.as_ref()
+ }
+}
+
+#[derive(Debug, Default)]
+pub struct Preedit {
+ /// The preedit text.
+ text: String,
+
+ /// Byte offset for cursor start into the preedit text.
+ ///
+ /// `None` means that the cursor is invisible.
+ cursor_byte_offset: Option<usize>,
+
+ /// The cursor offset from the end of the preedit in char width.
+ cursor_end_offset: Option<usize>,
+}
+
+impl Preedit {
+ pub fn new(text: String, cursor_byte_offset: Option<usize>) -> Self {
+ let cursor_end_offset = if let Some(byte_offset) = cursor_byte_offset {
+ // Convert byte offset into char offset.
+ let cursor_end_offset =
+ text[byte_offset..].chars().fold(0, |acc, ch| acc + ch.width().unwrap_or(1));
+
+ Some(cursor_end_offset)
+ } else {
+ None
+ };
+
+ Self { text, cursor_byte_offset, cursor_end_offset }
+ }
+}
+
/// Pending renderer updates.
///
/// All renderer updates are cached to be applied just before rendering, to avoid platform-specific
@@ -529,6 +604,7 @@ impl Display {
hint_state,
meter: Meter::new(),
size_info,
+ ime: Ime::new(),
highlighted_hint: None,
vi_highlighted_hint: None,
#[cfg(not(any(target_os = "macos", windows)))]
@@ -750,6 +826,7 @@ impl Display {
grid_cells.push(cell);
}
let selection_range = content.selection_range();
+ let foreground_color = content.color(NamedColor::Foreground as usize);
let background_color = content.color(NamedColor::Background as usize);
let display_offset = content.display_offset();
let cursor = content.cursor();
@@ -835,9 +912,7 @@ impl Display {
};
// Draw cursor.
- for rect in cursor.rects(&size_info, config.terminal_config.cursor.thickness()) {
- rects.push(rect);
- }
+ rects.extend(cursor.rects(&size_info, config.terminal_config.cursor.thickness()));
// Push visual bell after url/underline/strikeout rects.
let visual_bell_intensity = self.visual_bell.intensity();
@@ -853,6 +928,55 @@ impl Display {
rects.push(visual_bell_rect);
}
+ // Handle IME positioning and search bar rendering.
+ let ime_position = match search_state.regex() {
+ Some(regex) => {
+ let search_label = match search_state.direction() {
+ Direction::Right => FORWARD_SEARCH_LABEL,
+ Direction::Left => BACKWARD_SEARCH_LABEL,
+ };
+
+ let search_text = Self::format_search(regex, search_label, size_info.columns());
+
+ // Render the search bar.
+ self.draw_search(config, &search_text);
+
+ // Draw search bar cursor.
+ let line = size_info.screen_lines();
+ let column = Column(search_text.chars().count() - 1);
+
+ // Add cursor to search bar if IME is not active.
+ if self.ime.preedit().is_none() {
+ let fg = config.colors.footer_bar_foreground();
+ let shape = CursorShape::Underline;
+ let cursor = RenderableCursor::new(Point::new(line, column), shape, fg, false);
+ rects.extend(
+ cursor.rects(&size_info, config.terminal_config.cursor.thickness()),
+ );
+ }
+
+ Some(Point::new(line, column))
+ },
+ None => {
+ let num_lines = self.size_info.screen_lines();
+ term::point_to_viewport(display_offset, cursor_point)
+ .filter(|point| point.line < num_lines)
+ },
+ };
+
+ // Handle IME.
+ if self.ime.is_enabled() {
+ if let Some(point) = ime_position {
+ let (fg, bg) = if search_state.regex().is_some() {
+ (config.colors.footer_bar_foreground(), config.colors.footer_bar_background())
+ } else {
+ (foreground_color, background_color)
+ };
+
+ self.draw_ime_preview(point, fg, bg, &mut rects, config);
+ }
+ }
+
if self.debug_damage {
self.highlight_damage(&mut rects);
}
@@ -900,34 +1024,11 @@ impl Display {
self.draw_render_timer(config);
- // Handle search and IME positioning.
- let ime_position = match search_state.regex() {
- Some(regex) => {
- let search_label = match search_state.direction() {
- Direction::Right => FORWARD_SEARCH_LABEL,
- Direction::Left => BACKWARD_SEARCH_LABEL,
- };
-
- let search_text = Self::format_search(regex, search_label, size_info.columns());
-
- // Render the search bar.
- self.draw_search(config, &search_text);
-
- // Compute IME position.
- let line = Line(size_info.screen_lines() as i32 + 1);
- Point::new(line, Column(search_text.chars().count() - 1))
- },
- None => cursor_point,
- };
-
// Draw hyperlink uri preview.
if has_highlighted_hint {
self.draw_hyperlink_preview(config, vi_cursor_point, display_offset);
}
- // Update IME position.
- self.window.update_ime_position(ime_position, &self.size_info);
-
// Frame event should be requested before swaping buffers, since it requires surface
// `commit`, which is done by swap buffers under the hood.
#[cfg(all(feature = "wayland", not(any(target_os = "macos", windows))))]
@@ -1015,6 +1116,95 @@ impl Display {
dirty
}
+ #[inline(never)]
+ fn draw_ime_preview(
+ &mut self,
+ point: Point<usize>,
+ fg: Rgb,
+ bg: Rgb,
+ rects: &mut Vec<RenderRect>,
+ config: &UiConfig,
+ ) {
+ let preedit = match self.ime.preedit() {
+ Some(preedit) => preedit,
+ None => {
+ // In case we don't have preedit, just set the popup point.
+ self.window.update_ime_position(point, &self.size_info);
+ return;
+ },
+ };
+
+ let num_cols = self.size_info.columns();
+
+ // Get the visible preedit.
+ 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(
+ &preedit.text[byte_offset..],
+ num_cols,
+ ShortenDirection::Right,
+ Some(SHORTENER),
+ ),
+ _ => {
+ StrShortener::new(&preedit.text, num_cols, ShortenDirection::Left, Some(SHORTENER))
+ },
+ }
+ .collect();
+
+ let visible_len = visible_text.chars().count();
+
+ let end = cmp::min(point.column.0 + visible_len, num_cols);
+ let start = end.saturating_sub(visible_len);
+
+ let start = Point::new(point.line, Column(start));
+ let end = Point::new(point.line, Column(end - 1));
+
+ let glyph_cache = &mut self.glyph_cache;
+ let metrics = glyph_cache.font_metrics();
+
+ self.renderer.draw_string(
+ start,
+ fg,
+ bg,
+ visible_text.chars(),
+ &self.size_info,
+ glyph_cache,
+ );
+
+ if self.collect_damage() {
+ let damage = self.damage_from_point(Point::new(start.line, Column(0)), num_cols as u32);
+ self.damage_rects.push(damage);
+ self.next_frame_damage_rects.push(damage);
+ }
+
+ // Add underline for preedit text.
+ let underline = RenderLine { start, end, color: fg };
+ rects.extend(underline.rects(Flags::UNDERLINE, &metrics, &self.size_info));
+
+ let ime_popup_point = match preedit.cursor_end_offset {
+ Some(cursor_end_offset) if cursor_end_offset != 0 => {
+ let is_wide = preedit.text[preedit.cursor_byte_offset.unwrap_or_default()..]
+ .chars()
+ .next()
+ .map(|ch| ch.width() == Some(2))
+ .unwrap_or_default();
+
+ let cursor_column = Column(
+ (end.column.0 as isize - cursor_end_offset as isize + 1).max(0) as usize,
+ );
+ let cursor_point = Point::new(point.line, cursor_column);
+ let cursor =
+ RenderableCursor::new(cursor_point, CursorShape::HollowBlock, fg, is_wide);
+ rects.extend(
+ cursor.rects(&self.size_info, config.terminal_config.cursor.thickness()),
+ );
+ cursor_point
+ },
+ _ => end,
+ };
+
+ self.window.update_ime_position(ime_popup_point, &self.size_info);
+ }
+
/// Format search regex to account for the cursor and fullwidth characters.
fn format_search(search_regex: &str, search_label: &str, max_width: usize) -> String {
let label_len = search_label.len();
@@ -1033,7 +1223,8 @@ impl Display {
Some(SHORTENER),
));
- bar_text.push('_');
+ // Add place for cursor.
+ bar_text.push(' ');
bar_text
}
diff --git a/alacritty/src/display/window.rs b/alacritty/src/display/window.rs
index 3cc00a98..eac12a22 100644
--- a/alacritty/src/display/window.rs
+++ b/alacritty/src/display/window.rs
@@ -460,10 +460,14 @@ impl Window {
self.wayland_surface.as_ref()
}
+ pub fn set_ime_allowed(&self, allowed: bool) {
+ self.windowed_context.window().set_ime_allowed(allowed);
+ }
+
/// Adjust the IME editor position according to the new location of the cursor.
- pub fn update_ime_position(&self, point: Point, size: &SizeInfo) {
+ pub fn update_ime_position(&self, point: Point<usize>, size: &SizeInfo) {
let nspot_x = f64::from(size.padding_x() + point.column.0 as f32 * size.cell_width());
- let nspot_y = f64::from(size.padding_y() + (point.line.0 + 1) as f32 * size.cell_height());
+ let nspot_y = f64::from(size.padding_y() + (point.line + 1) as f32 * size.cell_height());
self.window().set_ime_position(PhysicalPosition::new(nspot_x, nspot_y));
}