Skip to content

Commit

Permalink
Render IME selected string in different color from other preedit string
Browse files Browse the repository at this point in the history
  • Loading branch information
kumattau committed May 6, 2024
1 parent 8fa4ba9 commit 00edfee
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 79 deletions.
2 changes: 1 addition & 1 deletion wezterm-gui/src/scripting/guiwin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ impl UserData for GuiWin {
.notify(TermWindowNotif::Apply(Box::new(move |term_window| {
tx.try_send(match term_window.composition_status() {
DeadKeyStatus::None => None,
DeadKeyStatus::Composing(s) => Some(s.clone()),
DeadKeyStatus::Composing(composing) => Some(composing.text.clone()),
})
.ok();
})));
Expand Down
3 changes: 2 additions & 1 deletion wezterm-gui/src/termwindow/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::shapecache::*;
use crate::termwindow::render::paint::AllowImage;
use crate::termwindow::{BorrowedShapeCacheKey, RenderState, ShapedInfo, TermWindowNotif};
use crate::utilsprites::RenderMetrics;
use crate::Composing;
use ::window::bitmaps::{TextureCoord, TextureRect, TextureSize};
use ::window::{DeadKeyStatus, PointF, RectF, SizeF, WindowOps};
use anyhow::{anyhow, Context};
Expand Down Expand Up @@ -56,7 +57,7 @@ pub struct LineQuadCacheKey {
pub shape_generation: usize,
pub quad_generation: usize,
/// Only set if cursor.y == stable_row
pub composing: Option<String>,
pub composing: Option<Composing>,
pub selection: Range<usize>,
pub shape_hash: [u8; 16],
pub top_pixel_y: NotNan<f32>,
Expand Down
6 changes: 3 additions & 3 deletions wezterm-gui/src/termwindow/render/pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ impl crate::TermWindow {
}),
match (self.pos.is_active, &self.term_window.dead_key_status) {
(true, DeadKeyStatus::Composing(composing)) => {
Some(composing.to_string())
Some(composing.clone())
}
_ => None,
},
Expand Down Expand Up @@ -430,7 +430,7 @@ impl crate::TermWindow {
config_generation: self.term_window.config.generation(),
shape_generation: self.term_window.shape_generation,
quad_generation: self.term_window.quad_generation,
composing: composing.clone(),
composing,
selection: selrange.clone(),
cursor,
shape_hash,
Expand Down Expand Up @@ -477,7 +477,7 @@ impl crate::TermWindow {
if let DeadKeyStatus::Composing(composing) =
&self.term_window.dead_key_status
{
Some((self.cursor.x, composing.to_string()))
Some((self.cursor.x, composing.text.clone()))
} else {
None
}
Expand Down
90 changes: 71 additions & 19 deletions wezterm-gui/src/termwindow/render/screen_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::termwindow::render::{
RenderScreenLineParams, RenderScreenLineResult,
};
use crate::termwindow::LineToElementShapeItem;
use crate::Composing;
use ::window::DeadKeyStatus;
use anyhow::Context;
use config::{HsbTransform, TextStyle};
Expand All @@ -17,6 +18,7 @@ use termwiz::surface::CursorShape;
use wezterm_bidi::Direction;
use wezterm_term::color::ColorAttribute;
use wezterm_term::CellAttributes;
use window::ComposingAttribute;

impl crate::TermWindow {
/// "Render" a line of the terminal screen into the vertex buffer.
Expand Down Expand Up @@ -64,6 +66,9 @@ impl crate::TermWindow {
let pos_y = (self.dimensions.pixel_height as f32 / -2.) + params.top_pixel_y;
let gl_x = self.dimensions.pixel_width as f32 / -2.;

let (_bidi_enabled, bidi_direction) = params.line.bidi_info();
let direction = bidi_direction.direction();

let start = Instant::now();

let cursor_idx = if params.pane.is_some()
Expand All @@ -75,25 +80,42 @@ impl crate::TermWindow {
None
};

let mut composing_text_width = 0;
let mut composing_selections = vec![];

// Referencing the text being composed, but only if it belongs to this pane
let composing = if cursor_idx.is_some() {
if let DeadKeyStatus::Composing(composing) = &self.dead_key_status {
Some(composing)
} else {
None
if cursor_idx.is_some() {
// Do we need to shape immediately, or can we use the pre-shaped data?
if let DeadKeyStatus::Composing(Composing { text, attr }) = &self.dead_key_status {
composing_text_width = unicode_column_width(text, None);

if let Some(attr) = attr {
// convert text and attr to selections
let mut selection = 0usize..0;
// iterate over byte end of each character in text
for (i, end) in text
.char_indices()
.map(|(offset, _)| offset)
.chain([text.len()])
.skip(1)
.take(attr.len())
.enumerate()
{
// update end to unicode width
let end = unicode_column_width(&text[..end], None);
if attr[i].contains(ComposingAttribute::SELECTED) {
selection.end = end;
}
// add non-empty selection and prepare next selection
if i + 1 == attr.len() || !attr[i].contains(ComposingAttribute::SELECTED) {
if !selection.is_empty() {
composing_selections.push(selection);
}
selection = end..end;
}
}
}
}
} else {
None
};

let mut composition_width = 0;

let (_bidi_enabled, bidi_direction) = params.line.bidi_info();
let direction = bidi_direction.direction();

// Do we need to shape immediately, or can we use the pre-shaped data?
if let Some(composing) = composing {
composition_width = unicode_column_width(composing, None);
}

let cursor_cell = if params.stable_line_idx == Some(params.cursor.y) {
Expand All @@ -102,8 +124,8 @@ impl crate::TermWindow {
None
};

let cursor_range = if composition_width > 0 {
params.cursor.x..params.cursor.x + composition_width
let cursor_range = if composing_text_width > 0 {
params.cursor.x..params.cursor.x + composing_text_width
} else if params.stable_line_idx == Some(params.cursor.y) {
params.cursor.x..params.cursor.x + cursor_cell.as_ref().map(|c| c.width()).unwrap_or(1)
} else {
Expand Down Expand Up @@ -419,6 +441,36 @@ impl crate::TermWindow {

quad.set_fg_color(cursor_border_color);
quad.set_alt_color_and_mix_value(cursor_border_color_alt, cursor_border_mix);

for selection in &composing_selections {
let selection = params.cursor.x + selection.start - cursor_range.start
..params.cursor.x + selection.end - cursor_range.start;
if !selection.is_empty() {
let mut quad = layers.allocate(0)?;
quad.set_position(
pos_x + selection.start as f32 * cell_width,
pos_y,
pos_x + selection.end as f32 * cell_width,
pos_y + cell_height,
);
quad.set_hsv(hsv);
quad.set_has_color(false);

quad.set_texture(
gl_state
.glyph_cache
.borrow_mut()
.cursor_sprite(
cursor_shape,
&params.render_metrics,
(selection.end - selection.start) as u8,
)?
.texture_coords(),
);

quad.set_fg_color(params.selection_bg);
}
}
}
}

Expand Down
18 changes: 17 additions & 1 deletion window/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,23 @@ pub enum DeadKeyStatus {
None,
/// Holding until composition is done; the string is the uncommitted
/// composition text to show as a placeholder
Composing(String),
Composing(Composing),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Composing {
/// Holding composing text
pub text: String,
/// Holding composing attribute of each unicode character in composing text
pub attr: Option<Vec<ComposingAttribute>>,
}

bitflags! {
#[derive(Default)]
pub struct ComposingAttribute: u8 {
const NONE = 0;
const SELECTED = 1;
}
}

#[derive(Debug)]
Expand Down
56 changes: 38 additions & 18 deletions window/src/os/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use crate::connection::ConnectionOps;
use crate::os::macos::menu::{MenuItem, RepresentedItem};
use crate::parameters::{Border, Parameters, TitleBar};
use crate::{
Clipboard, Connection, DeadKeyStatus, Dimensions, Handled, KeyCode, KeyEvent, Modifiers,
MouseButtons, MouseCursor, MouseEvent, MouseEventKind, MousePress, Point, RawKeyEvent, Rect,
RequestedWindowGeometry, ResizeIncrement, ResolvedGeometry, ScreenPoint, Size, ULength,
WindowDecorations, WindowEvent, WindowEventSender, WindowOps, WindowState,
Clipboard, Composing, ComposingAttribute, Connection, DeadKeyStatus, Dimensions, Handled,
KeyCode, KeyEvent, Modifiers, MouseButtons, MouseCursor, MouseEvent, MouseEventKind,
MousePress, Point, RawKeyEvent, Rect, RequestedWindowGeometry, ResizeIncrement,
ResolvedGeometry, ScreenPoint, Size, ULength, WindowDecorations, WindowEvent,
WindowEventSender, WindowOps, WindowState,
};
use anyhow::{anyhow, bail, ensure};
use async_trait::async_trait;
Expand Down Expand Up @@ -500,7 +501,7 @@ impl Window {
ime_state: ImeDisposition::None,
ime_last_event: None,
live_resizing: false,
ime_text: String::new(),
ime_composing: Default::default(),
}));

let window: id = msg_send![get_window_class(), alloc];
Expand Down Expand Up @@ -1440,7 +1441,7 @@ struct Inner {
/// Whether we're in live resize
live_resizing: bool,

ime_text: String,
ime_composing: Composing,
}

#[repr(C)]
Expand Down Expand Up @@ -1822,7 +1823,7 @@ impl WindowView {
extern "C" fn has_marked_text(this: &mut Object, _sel: Sel) -> BOOL {
if let Some(myself) = Self::get_this(this) {
let inner = myself.inner.borrow();
if inner.ime_text.is_empty() {
if inner.ime_composing.text.is_empty() {
NO
} else {
YES
Expand All @@ -1835,11 +1836,11 @@ impl WindowView {
extern "C" fn marked_range(this: &mut Object, _sel: Sel) -> NSRange {
if let Some(myself) = Self::get_this(this) {
let inner = myself.inner.borrow();
log::trace!("marked_range {:?}", inner.ime_text);
if inner.ime_text.is_empty() {
log::trace!("marked_range {:?}", inner.ime_composing);
if inner.ime_composing.text.is_empty() {
NSRange::new(NSNotFound as _, 0)
} else {
NSRange::new(0, inner.ime_text.len() as u64)
NSRange::new(0, inner.ime_composing.text.len() as u64)
}
} else {
NSRange::new(NSNotFound as _, 0)
Expand Down Expand Up @@ -1879,7 +1880,7 @@ impl WindowView {
raw: None,
};

inner.ime_text.clear();
inner.ime_composing = Default::default();
inner
.events
.dispatch(WindowEvent::AdviseDeadKeyStatus(DeadKeyStatus::None));
Expand All @@ -1905,7 +1906,23 @@ impl WindowView {
);
if let Some(myself) = Self::get_this(this) {
let mut inner = myself.inner.borrow_mut();
inner.ime_text = s.to_string();

let text = s.to_string();
let attr = Some(
text.chars()
.scan(0, |i, c| (Some(*i), *i += c.len_utf16()).0)
.map(|i| {
let mut attr = ComposingAttribute::NONE;
if selected_range.0.location <= (i as u64)
&& (i as u64) < selected_range.0.location + selected_range.0.length
{
attr |= ComposingAttribute::SELECTED;
}
attr
})
.collect(),
);
inner.ime_composing = Composing { text, attr };

/*
let key_is_down = inner.key_is_down.take().unwrap_or(true);
Expand Down Expand Up @@ -1935,7 +1952,7 @@ impl WindowView {
// FIXME: docs say to insert the text here,
// but iterm doesn't... and we've never seen
// this get called so far?
inner.ime_text.clear();
inner.ime_composing = Default::default();
inner.ime_last_event.take();
inner.ime_state = ImeDisposition::Acted;
}
Expand Down Expand Up @@ -2432,7 +2449,10 @@ impl WindowView {
Ok(TranslateStatus::Composing(composing)) => {
// Next key press in dead key sequence is pending.
inner.events.dispatch(WindowEvent::AdviseDeadKeyStatus(
DeadKeyStatus::Composing(composing),
DeadKeyStatus::Composing(Composing {
text: composing,
attr: None,
}),
));

return;
Expand Down Expand Up @@ -2513,7 +2533,7 @@ impl WindowView {
let mut inner = myself.inner.borrow_mut();
inner.key_is_down.replace(key_is_down);
inner.ime_state = ImeDisposition::None;
inner.ime_text.clear();
inner.ime_composing = Default::default();
}

unsafe {
Expand Down Expand Up @@ -2541,7 +2561,7 @@ impl WindowView {
// If it didn't generate an event, then a composition
// is pending.
let status = if inner.ime_last_event.is_none() {
DeadKeyStatus::Composing(inner.ime_text.clone())
DeadKeyStatus::Composing(inner.ime_composing.clone())
} else {
DeadKeyStatus::None
};
Expand All @@ -2568,10 +2588,10 @@ impl WindowView {
return;
}
}
let status = if inner.ime_text.is_empty() {
let status = if inner.ime_composing.text.is_empty() {
DeadKeyStatus::None
} else {
DeadKeyStatus::Composing(inner.ime_text.clone())
DeadKeyStatus::Composing(inner.ime_composing.clone())
};
inner
.events
Expand Down
4 changes: 2 additions & 2 deletions window/src/os/wayland/inputhandler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use wayland_protocols::wp::text_input::zv3::client::zwp_text_input_v3::{
};
use wezterm_input_types::{KeyCode, KeyEvent, KeyboardLedStatus, Modifiers};

use crate::{DeadKeyStatus, WindowEvent};
use crate::{Composing, DeadKeyStatus, WindowEvent};

use super::state::WaylandState;

Expand Down Expand Up @@ -190,7 +190,7 @@ impl Dispatch<ZwpTextInputV3, TextInputData, WaylandState> for TextInputState {
}));
}
let status = if let Some(text) = pending_state.pre_edit.take() {
DeadKeyStatus::Composing(text)
DeadKeyStatus::Composing(Composing { text, attr: None })
} else {
DeadKeyStatus::None
};
Expand Down
Loading

0 comments on commit 00edfee

Please sign in to comment.