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 Apr 28, 2024
1 parent cce0706 commit af312b6
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 35 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(s, ..) => Some(s.clone()),
})
.ok();
})));
Expand Down
4 changes: 2 additions & 2 deletions wezterm-gui/src/termwindow/render/pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ impl crate::TermWindow {
cursor_is_default_color: self.cursor_is_default_color,
}),
match (self.pos.is_active, &self.term_window.dead_key_status) {
(true, DeadKeyStatus::Composing(composing)) => {
(true, DeadKeyStatus::Composing(composing, _)) => {
Some(composing.to_string())
}
_ => None,
Expand Down Expand Up @@ -474,7 +474,7 @@ impl crate::TermWindow {
shape_hash,
shape_generation: quad_key.shape_generation,
composing: if self.cursor.y == stable_row && self.pos.is_active {
if let DeadKeyStatus::Composing(composing) =
if let DeadKeyStatus::Composing(composing, _) =
&self.term_window.dead_key_status
{
Some((self.cursor.x, composing.to_string()))
Expand Down
55 changes: 50 additions & 5 deletions wezterm-gui/src/termwindow/render/screen_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,18 @@ impl crate::TermWindow {
};

// 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)
let (composing, selected_range) = if cursor_idx.is_some() {
if let DeadKeyStatus::Composing(composing, selected_range) = &self.dead_key_status {
(Some(composing), selected_range.as_ref())
} else {
None
(None, None)
}
} else {
None
(None, None)
};

let mut selected_start = 0;
let mut selected_width = 0;
let mut composition_width = 0;

let (_bidi_enabled, bidi_direction) = params.line.bidi_info();
Expand All @@ -94,6 +96,11 @@ impl crate::TermWindow {
// 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);

if let Some(selected_range) = selected_range {
selected_start = unicode_column_width(&composing[..selected_range.start], None);
selected_width = unicode_column_width(&composing[selected_range.clone()], None);
}
}

let cursor_cell = if params.stable_line_idx == Some(params.cursor.y) {
Expand All @@ -110,6 +117,12 @@ impl crate::TermWindow {
0..0
};

let selected_cursor_range = if selected_width > 0 {
params.cursor.x + selected_start..params.cursor.x + selected_start + selected_width
} else {
0..0
};

let cursor_range_pixels = params.left_pixel_x + cursor_range.start as f32 * cell_width
..params.left_pixel_x + cursor_range.end as f32 * cell_width;

Expand Down Expand Up @@ -419,6 +432,38 @@ impl crate::TermWindow {

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

if !selected_cursor_range.is_empty()
&& (cursor_range.start <= selected_cursor_range.start)
&& (selected_cursor_range.end <= cursor_range.end)
{
let mut quad = layers.allocate(0)?;
quad.set_position(
pos_x
+ (selected_cursor_range.start - cursor_range.start) as f32
* cell_width,
pos_y,
pos_x
+ (selected_cursor_range.end - cursor_range.start) 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,
(selected_cursor_range.end - selected_cursor_range.start) as u8,
)?
.texture_coords(),
);

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

Expand Down
3 changes: 2 additions & 1 deletion window/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use config::window::WindowLevel;
use config::{ConfigHandle, Dimension, GeometryOrigin};
use promise::Future;
use std::any::Any;
use std::ops::Range;
use std::path::PathBuf;
use std::rc::Rc;
use thiserror::Error;
Expand Down Expand Up @@ -150,7 +151,7 @@ pub enum DeadKeyStatus {
None,
/// Holding until composition is done; the string is the uncommitted
/// composition text to show as a placeholder
Composing(String),
Composing(String, Option<Range<usize>>),
}

#[derive(Debug)]
Expand Down
7 changes: 6 additions & 1 deletion window/src/os/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@ fn nsstring(s: &str) -> StrongPtr {
unsafe { StrongPtr::new(NSString::alloc(nil).init_str(s)) }
}

unsafe fn nsstring_to_str<'a>(mut ns: *mut Object) -> &'a str {
unsafe fn unattributed(mut ns: *mut Object) -> *mut Object {
let is_astring: bool = msg_send![ns, isKindOfClass: class!(NSAttributedString)];
if is_astring {
ns = msg_send![ns, string];
}
ns
}

unsafe fn nsstring_to_str<'a>(mut ns: *mut Object) -> &'a str {
ns = unattributed(ns);
let data = NSString::UTF8String(ns as id) as *const u8;
let len = NSString::len(ns as id);
let bytes = std::slice::from_raw_parts(data, len);
Expand Down
63 changes: 59 additions & 4 deletions window/src/os/macos/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![allow(clippy::let_unit_value)]

use super::keycodes::*;
use super::{nsstring, nsstring_to_str};
use super::{nsstring, nsstring_to_str, unattributed};
use crate::clipboard::Clipboard as ClipboardContext;
use crate::connection::ConnectionOps;
use crate::os::macos::menu::{MenuItem, RepresentedItem};
Expand Down Expand Up @@ -45,6 +45,7 @@ use raw_window_handle::{
use std::any::Any;
use std::cell::RefCell;
use std::ffi::c_void;
use std::ops::Range;
use std::path::PathBuf;
use std::rc::Rc;
use std::str::FromStr;
Expand Down Expand Up @@ -501,6 +502,7 @@ impl Window {
ime_last_event: None,
live_resizing: false,
ime_text: String::new(),
selected_range: None,
}));

let window: id = msg_send![get_window_class(), alloc];
Expand Down Expand Up @@ -1441,6 +1443,7 @@ struct Inner {
live_resizing: bool,

ime_text: String,
selected_range: Option<Range<usize>>,
}

#[repr(C)]
Expand Down Expand Up @@ -1906,6 +1909,7 @@ impl WindowView {
if let Some(myself) = Self::get_this(this) {
let mut inner = myself.inner.borrow_mut();
inner.ime_text = s.to_string();
inner.selected_range = calc_str_selected_range(astring, selected_range, s.len());

/*
let key_is_down = inner.key_is_down.take().unwrap_or(true);
Expand Down Expand Up @@ -2432,7 +2436,7 @@ 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, None),
));

return;
Expand Down Expand Up @@ -2541,7 +2545,10 @@ 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_text.clone(),
inner.selected_range.clone(),
)
} else {
DeadKeyStatus::None
};
Expand Down Expand Up @@ -2571,7 +2578,10 @@ impl WindowView {
let status = if inner.ime_text.is_empty() {
DeadKeyStatus::None
} else {
DeadKeyStatus::Composing(inner.ime_text.clone())
DeadKeyStatus::Composing(
inner.ime_text.clone(),
inner.selected_range.clone(),
)
};
inner
.events
Expand Down Expand Up @@ -3300,3 +3310,48 @@ impl WindowView {
cls.register()
}
}

fn calc_str_selected_range(
astring: *mut Object,
selected_range: NSRange,
max: usize,
) -> Option<Range<usize>> {
unsafe fn sub_tostr_len(ns: *mut Object, range: NSRange, max: usize) -> usize {
let sub = msg_send![ns, substringWithRange: range];
let len = nsstring_to_str(sub).len();
std::cmp::min(len, max)
}

let ns;
let ns_length: u64;
unsafe {
ns = unattributed(astring);
ns_length = msg_send![ns, length];
}

let a_start = selected_range.0.location;
let a_end = a_start + selected_range.0.length;
if ns_length < a_end {
return None;
}

let s_start;
let s_end;
unsafe {
s_start = if 0 < a_start {
sub_tostr_len(ns, NSRange::new(0, a_start), max)
} else {
0
};
s_end = if a_end < ns_length {
s_start + sub_tostr_len(ns, NSRange::new(a_start, a_end - a_start), max)
} else {
max
};
}
if s_end <= s_start {
return None;
}

Some(s_start..s_end)
}
2 changes: 1 addition & 1 deletion window/src/os/wayland/inputhandler.rs
Original file line number Diff line number Diff line change
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(text, None)
} else {
DeadKeyStatus::None
};
Expand Down
Loading

0 comments on commit af312b6

Please sign in to comment.