Skip to content

Commit

Permalink
Add IME preedit ranges
Browse files Browse the repository at this point in the history
  • Loading branch information
Riateche committed Aug 25, 2024
1 parent e16b39f commit db60c28
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ modit = { version = "0.1.4", optional = true }
rangemap = "1.4.0"
rustc-hash = { version = "1.1.0", default-features = false }
rustybuzz = { version = "0.14", default-features = false, features = ["libm"] }
itertools = "0.11.0"
self_cell = "1.0.1"
swash = { version = "0.1.17", optional = true }
syntect = { version = "5.1.0", optional = true }
Expand Down
18 changes: 17 additions & 1 deletion examples/editor/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::{env, fs, num::NonZeroU32, rc::Rc, slice};
use tiny_skia::{Paint, PixmapMut, Rect, Transform};
use winit::{
dpi::{PhysicalPosition, PhysicalSize},
event::{ElementState, Event, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent},
event::{ElementState, Event, Ime, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent},
event_loop::{ControlFlow, EventLoop},
keyboard::{Key, NamedKey},
window::WindowBuilder,
Expand All @@ -28,6 +28,7 @@ fn main() {
let mut swash_cache = SwashCache::new();

let mut display_scale = window.scale_factor() as f32;
window.set_ime_allowed(true);

let scrollbar_width = 12.0;
let font_sizes = [
Expand Down Expand Up @@ -278,6 +279,21 @@ fn main() {
window.request_redraw();
}
}
WindowEvent::Ime(ime) => match ime {
Ime::Enabled | Ime::Disabled => {}
Ime::Preedit(preedit, cursor) => {
editor.action(Action::SetPreedit {
preedit,
cursor,
attrs: None,
});
window.request_redraw();
}
Ime::Commit(text) => {
editor.insert_string(&text, None);
window.request_redraw();
}
},
WindowEvent::CursorMoved {
device_id: _,
position,
Expand Down
11 changes: 11 additions & 0 deletions src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ pub struct Attrs<'a> {
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
pub is_preedit: bool,
}

impl<'a> Attrs<'a> {
Expand All @@ -155,6 +156,7 @@ impl<'a> Attrs<'a> {
metadata: 0,
cache_key_flags: CacheKeyFlags::empty(),
metrics_opt: None,
is_preedit: false,
}
}

Expand Down Expand Up @@ -206,6 +208,12 @@ impl<'a> Attrs<'a> {
self
}

/// Set preedit
pub fn preedit(mut self, is_preedit: bool) -> Self {
self.is_preedit = is_preedit;
self
}

/// Check if font matches
pub fn matches(&self, face: &fontdb::FaceInfo) -> bool {
//TODO: smarter way of including emoji
Expand Down Expand Up @@ -254,6 +262,7 @@ pub struct AttrsOwned {
pub metadata: usize,
pub cache_key_flags: CacheKeyFlags,
pub metrics_opt: Option<CacheMetrics>,
pub is_preedit: bool,
}

impl AttrsOwned {
Expand All @@ -267,6 +276,7 @@ impl AttrsOwned {
metadata: attrs.metadata,
cache_key_flags: attrs.cache_key_flags,
metrics_opt: attrs.metrics_opt,
is_preedit: attrs.is_preedit,
}
}

Expand All @@ -280,6 +290,7 @@ impl AttrsOwned {
metadata: self.metadata,
cache_key_flags: self.cache_key_flags,
metrics_opt: self.metrics_opt,
is_preedit: self.is_preedit,
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::{cmp, fmt};
use itertools::Itertools;
use unicode_segmentation::UnicodeSegmentation;

use crate::{
Expand Down Expand Up @@ -811,6 +812,14 @@ impl Buffer {
self.shape_until_scroll(font_system, false);
}

/// Returns text of the buffer, excluding preedit (if any)
pub fn text_without_preedit(&self) -> String {
self.lines
.iter()
.map(|line| line.text_without_preedit())
.join("\n")
}

/// True if a redraw is needed
pub fn redraw(&self) -> bool {
self.redraw
Expand Down
37 changes: 35 additions & 2 deletions src/buffer_line.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use core::ops::Range;

use alloc::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use alloc::{
string::{String, ToString},
vec::Vec,
};

use crate::{
Align, AttrsList, FontSystem, LayoutLine, LineEnding, ShapeBuffer, ShapeLine, Shaping, Wrap,
Expand Down Expand Up @@ -40,11 +46,38 @@ impl BufferLine {
}
}

/// Get current text
/// Get current text, including preedit (if any)
pub fn text(&self) -> &str {
&self.text
}

/// Returns the range of byte indices of the text corresponding
/// to the preedit
pub fn preedit_range(&self) -> Option<Range<usize>> {
self.attrs_list
.spans()
.iter()
.find(|(_, attrs)| attrs.is_preedit)
.map(|(range, _)| (*range).clone())
}

/// Get current preedit text
pub fn preedit_text(&self) -> Option<&str> {
let range = self.preedit_range()?;
Some(&self.text[range])
}

/// Get current text, excluding preedit (if any)
pub fn text_without_preedit(&self) -> Cow<'_, str> {
if let Some(range) = self.preedit_range() {
let mut text = self.text[..range.start].to_string();
text.push_str(&self.text[range.end..]);
text.into()
} else {
self.text.as_str().into()
}
}

/// Set text and attributes list
///
/// Will reset shape and layout if it differs from current text and attributes list.
Expand Down
65 changes: 64 additions & 1 deletion src/edit/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::{cmp, iter::once};
use core::{cmp, iter::once, ops::Range};
use unicode_segmentation::UnicodeSegmentation;

#[cfg(feature = "swash")]
Expand Down Expand Up @@ -561,6 +561,18 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
self.change.take()
}

fn preedit_range(&self) -> Option<Range<usize>> {
self.with_buffer(|buffer| buffer.lines[self.cursor.line].preedit_range())
}

fn preedit_text(&self) -> Option<String> {
self.with_buffer(|buffer| {
buffer.lines[self.cursor.line]
.preedit_text()
.map(Into::into)
})
}

fn action(&mut self, font_system: &mut FontSystem, action: Action) {
let old_cursor = self.cursor;

Expand Down Expand Up @@ -865,6 +877,57 @@ impl<'buffer> Edit<'buffer> for Editor<'buffer> {
buffer.set_scroll(scroll);
});
}
Action::SetPreedit {
preedit,
cursor,
attrs,
} => {
self.selection = Selection::None;
let mut self_cursor = self.cursor;

// Remove old preedit, if any
self.with_buffer_mut(|buffer| {
let line: &mut BufferLine = &mut buffer.lines[self_cursor.line];
if let Some(range) = line.preedit_range() {
let end = line.split_off(range.end);
line.split_off(range.start);
line.append(end);
self_cursor.index = range.start;
}
});
self.cursor = self_cursor;

if !preedit.is_empty() {
let new_attrs = if let Some(attrs) = attrs {
AttrsList::new(attrs.as_attrs().preedit(true))
} else {
self.with_buffer(|buffer| {
let attrs_at_cursor = buffer.lines[self_cursor.line]
.attrs_list()
.get_span(self_cursor.index);
AttrsList::new(
attrs_at_cursor
.preedit(true)
.color(Color::rgb(128, 128, 128)),
)
})
};
self.insert_string(&preedit, Some(new_attrs));
if let Some((start, end)) = cursor {
let end_delta = preedit.len().saturating_sub(end);
self.cursor.index = self.cursor.index.saturating_sub(end_delta);
if start != end {
let start_delta = preedit.len().saturating_sub(start);
let mut select = self.cursor;
select.index = select.index.saturating_sub(start_delta);
self.selection = Selection::Normal(select);
}
} else {
// TODO: hide cursor
}
}
self.set_redraw(true);
}
}

if old_cursor != self.cursor {
Expand Down
27 changes: 25 additions & 2 deletions src/edit/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use alloc::sync::Arc;
use core::ops::Range;

#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};
use core::cmp;
use unicode_segmentation::UnicodeSegmentation;

use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
use crate::{AttrsList, AttrsOwned, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};

pub use self::editor::*;
mod editor;
Expand All @@ -20,7 +22,7 @@ pub use self::vi::*;
mod vi;

/// An action to perform on an [`Editor`]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Action {
/// Move the cursor with some motion
Motion(Motion),
Expand Down Expand Up @@ -62,6 +64,20 @@ pub enum Action {
Scroll {
lines: i32,
},
/// Set preedit text, replacing any previous preedit text
///
/// If `cursor` is specified, it contains a start and end cursor byte positions
/// within the preedit. If no cursor is specified for a non-empty preedit,
/// the cursor should be hidden.
///
/// If `attrs` is specified, these attributes will be assigned to the preedit's span.
/// However, regardless of `attrs` setting, the preedit's span will always have
/// `is_preedit` set to `true`.
SetPreedit {
preedit: String,
cursor: Option<(usize, usize)>,
attrs: Option<AttrsOwned>,
},
}

#[derive(Debug)]
Expand Down Expand Up @@ -327,6 +343,13 @@ pub trait Edit<'buffer> {
/// Get completed change
fn finish_change(&mut self) -> Option<Change>;

/// Returns the range of byte indices of the text corresponding
/// to the preedit
fn preedit_range(&self) -> Option<Range<usize>>;

/// Get current preedit text
fn preedit_text(&self) -> Option<String>;

/// Perform an [Action] on the editor
fn action(&mut self, font_system: &mut FontSystem, action: Action);

Expand Down
11 changes: 11 additions & 0 deletions src/edit/syntect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,9 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for SyntaxEditor<'syntax_system, 'bu
}

let line = &mut buffer.lines[line_i];
if line.preedit_range().is_some() {
continue;
}
if line.metadata().is_some() && line_i < self.syntax_cache.len() {
//TODO: duplicated code!
if line_i >= scroll.line && total_height < scroll_end {
Expand Down Expand Up @@ -427,6 +430,14 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for SyntaxEditor<'syntax_system, 'bu
self.editor.start_change();
}

fn preedit_range(&self) -> Option<core::ops::Range<usize>> {
self.editor.preedit_range()
}

fn preedit_text(&self) -> Option<String> {
self.editor.preedit_text()
}

fn finish_change(&mut self) -> Option<Change> {
self.editor.finish_change()
}
Expand Down
8 changes: 8 additions & 0 deletions src/edit/vi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,14 @@ impl<'syntax_system, 'buffer> Edit<'buffer> for ViEditor<'syntax_system, 'buffer
fn cursor_position(&self) -> Option<(i32, i32)> {
self.editor.cursor_position()
}

fn preedit_range(&self) -> Option<core::ops::Range<usize>> {
self.editor.preedit_range()
}

fn preedit_text(&self) -> Option<String> {
self.editor.preedit_text()
}
}

impl<'font_system, 'syntax_system, 'buffer>
Expand Down

0 comments on commit db60c28

Please sign in to comment.