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

Add IME preedit ranges #261

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
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
17 changes: 17 additions & 0 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -811,6 +811,23 @@ 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 {
let mut output = String::new();
let mut iter = self
.lines
.iter()
.map(|line| line.text_without_preedit())
.peekable();
while let Some(text) = iter.next() {
output.push_str(&text);
if iter.peek().is_some() {
output.push('\n');
}
}
output
}

/// 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
69 changes: 65 additions & 4 deletions src/edit/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ 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")]
use crate::Color;
use crate::{
Action, Attrs, AttrsList, BorrowedWithFontSystem, BufferLine, BufferRef, Change, ChangeItem,
Cursor, Edit, FontSystem, LayoutRun, Selection, Shaping,
Color, Cursor, Edit, FontSystem, LayoutRun, Selection, Shaping,
};

/// A wrapper of [`Buffer`] for easy editing
Expand Down Expand Up @@ -561,6 +559,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 +875,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
Loading