Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IME Selected String #2056

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
97 changes: 78 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,49 @@ 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 SELECTED 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()
{
// check last character/attr or SELECTED switch
let last = end == text.len() || i + 1 == attr.len();
if last || (attr[i] ^ attr[i + 1]).contains(ComposingAttribute::SELECTED) {
// update end to unicode width
let end = unicode_column_width(&text[..end], None);
// add selection to selections if attr[i] is end of SELECTED
if attr[i].contains(ComposingAttribute::SELECTED) {
selection.end = end;
if !selection.is_empty() {
composing_selections.push(selection);
}
}
// break if last character/attr is processed
if last {
break;
}
// prepare selection for next SELECTED or start of SELECTED
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 +131,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 +448,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
Loading