Move cursor motions to new Motion enum, move handling to Buffer
This commit is contained in:
parent
6528e9f804
commit
018a2e9d2a
10 changed files with 567 additions and 505 deletions
|
|
@ -5,7 +5,7 @@ use cosmic::{
|
||||||
iced_runtime::keyboard::KeyCode,
|
iced_runtime::keyboard::KeyCode,
|
||||||
theme::{Theme, ThemeType},
|
theme::{Theme, ThemeType},
|
||||||
};
|
};
|
||||||
use cosmic_text::{Action, Edit, SwashCache};
|
use cosmic_text::{Action, Edit, Motion, SwashCache};
|
||||||
use std::{cmp, sync::Mutex, time::Instant};
|
use std::{cmp, sync::Mutex, time::Instant};
|
||||||
|
|
||||||
use crate::FONT_SYSTEM;
|
use crate::FONT_SYSTEM;
|
||||||
|
|
@ -281,35 +281,35 @@ where
|
||||||
match event {
|
match event {
|
||||||
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => match key_code {
|
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => match key_code {
|
||||||
KeyCode::Left => {
|
KeyCode::Left => {
|
||||||
editor.action(Action::Left);
|
editor.action(Action::Motion(Motion::Left));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::Right => {
|
KeyCode::Right => {
|
||||||
editor.action(Action::Right);
|
editor.action(Action::Motion(Motion::Right));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::Up => {
|
KeyCode::Up => {
|
||||||
editor.action(Action::Up);
|
editor.action(Action::Motion(Motion::Up));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
editor.action(Action::Down);
|
editor.action(Action::Motion(Motion::Down));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::Home => {
|
KeyCode::Home => {
|
||||||
editor.action(Action::Home);
|
editor.action(Action::Motion(Motion::Home));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::End => {
|
KeyCode::End => {
|
||||||
editor.action(Action::End);
|
editor.action(Action::Motion(Motion::End));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::PageUp => {
|
KeyCode::PageUp => {
|
||||||
editor.action(Action::PageUp);
|
editor.action(Action::Motion(Motion::PageUp));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::PageDown => {
|
KeyCode::PageDown => {
|
||||||
editor.action(Action::PageDown);
|
editor.action(Action::Motion(Motion::PageDown));
|
||||||
status = Status::Captured;
|
status = Status::Captured;
|
||||||
}
|
}
|
||||||
KeyCode::Escape => {
|
KeyCode::Escape => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, SwashCache, SyntaxEditor,
|
Action, Attrs, Buffer, Edit, Family, FontSystem, Metrics, Motion, SwashCache, SyntaxEditor,
|
||||||
SyntaxSystem,
|
SyntaxSystem,
|
||||||
};
|
};
|
||||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||||
|
|
@ -147,14 +147,26 @@ fn main() {
|
||||||
match event.to_option() {
|
match event.to_option() {
|
||||||
EventOption::Key(event) => match event.scancode {
|
EventOption::Key(event) => match event.scancode {
|
||||||
orbclient::K_CTRL => ctrl_pressed = event.pressed,
|
orbclient::K_CTRL => ctrl_pressed = event.pressed,
|
||||||
orbclient::K_LEFT if event.pressed => editor.action(Action::Left),
|
orbclient::K_LEFT if event.pressed => {
|
||||||
orbclient::K_RIGHT if event.pressed => editor.action(Action::Right),
|
editor.action(Action::Motion(Motion::Left))
|
||||||
orbclient::K_UP if event.pressed => editor.action(Action::Up),
|
}
|
||||||
orbclient::K_DOWN if event.pressed => editor.action(Action::Down),
|
orbclient::K_RIGHT if event.pressed => {
|
||||||
orbclient::K_HOME if event.pressed => editor.action(Action::Home),
|
editor.action(Action::Motion(Motion::Right))
|
||||||
orbclient::K_END if event.pressed => editor.action(Action::End),
|
}
|
||||||
orbclient::K_PGUP if event.pressed => editor.action(Action::PageUp),
|
orbclient::K_UP if event.pressed => editor.action(Action::Motion(Motion::Up)),
|
||||||
orbclient::K_PGDN if event.pressed => editor.action(Action::PageDown),
|
orbclient::K_DOWN if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::Down))
|
||||||
|
}
|
||||||
|
orbclient::K_HOME if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::Home))
|
||||||
|
}
|
||||||
|
orbclient::K_END if event.pressed => editor.action(Action::Motion(Motion::End)),
|
||||||
|
orbclient::K_PGUP if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::PageUp))
|
||||||
|
}
|
||||||
|
orbclient::K_PGDN if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::PageDown))
|
||||||
|
}
|
||||||
orbclient::K_ESC if event.pressed => editor.action(Action::Escape),
|
orbclient::K_ESC if event.pressed => editor.action(Action::Escape),
|
||||||
orbclient::K_ENTER if event.pressed => editor.action(Action::Enter),
|
orbclient::K_ENTER if event.pressed => editor.action(Action::Enter),
|
||||||
orbclient::K_BKSP if event.pressed => editor.action(Action::Backspace),
|
orbclient::K_BKSP if event.pressed => editor.action(Action::Backspace),
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Action, BidiParagraphs, BorrowedWithFontSystem, Buffer, Color, Edit, Editor, FontSystem,
|
Action, BidiParagraphs, BorrowedWithFontSystem, Buffer, Color, Edit, Editor, FontSystem,
|
||||||
Metrics, SwashCache,
|
Metrics, Motion, SwashCache,
|
||||||
};
|
};
|
||||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{env, fs, process, time::Instant};
|
use std::{env, fs, process, time::Instant};
|
||||||
|
|
@ -106,7 +106,7 @@ fn main() {
|
||||||
// Test delete of EGC
|
// Test delete of EGC
|
||||||
{
|
{
|
||||||
let cursor = editor.cursor();
|
let cursor = editor.cursor();
|
||||||
editor.action(Action::Previous);
|
editor.action(Action::Motion(Motion::Previous));
|
||||||
editor.action(Action::Delete);
|
editor.action(Action::Delete);
|
||||||
for c in grapheme.chars() {
|
for c in grapheme.chars() {
|
||||||
editor.action(Action::Insert(c));
|
editor.action(Action::Insert(c));
|
||||||
|
|
@ -130,7 +130,7 @@ fn main() {
|
||||||
{
|
{
|
||||||
let cursor = editor.cursor();
|
let cursor = editor.cursor();
|
||||||
editor.action(Action::Enter);
|
editor.action(Action::Enter);
|
||||||
editor.action(Action::Previous);
|
editor.action(Action::Motion(Motion::Previous));
|
||||||
editor.action(Action::Delete);
|
editor.action(Action::Delete);
|
||||||
assert_eq!(cursor, editor.cursor());
|
assert_eq!(cursor, editor.cursor());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// SPDX-License-Identifier: MIT OR Apache-2.0
|
// SPDX-License-Identifier: MIT OR Apache-2.0
|
||||||
|
|
||||||
use cosmic_text::{
|
use cosmic_text::{
|
||||||
Action, Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Shaping, Style,
|
Action, Attrs, Buffer, Color, Edit, Editor, Family, FontSystem, Metrics, Motion, Shaping,
|
||||||
SwashCache, Weight,
|
Style, SwashCache, Weight,
|
||||||
};
|
};
|
||||||
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
use orbclient::{EventOption, Renderer, Window, WindowFlag};
|
||||||
use std::{
|
use std::{
|
||||||
|
|
@ -152,14 +152,26 @@ fn main() {
|
||||||
for event in window.events() {
|
for event in window.events() {
|
||||||
match event.to_option() {
|
match event.to_option() {
|
||||||
EventOption::Key(event) => match event.scancode {
|
EventOption::Key(event) => match event.scancode {
|
||||||
orbclient::K_LEFT if event.pressed => editor.action(Action::Left),
|
orbclient::K_LEFT if event.pressed => {
|
||||||
orbclient::K_RIGHT if event.pressed => editor.action(Action::Right),
|
editor.action(Action::Motion(Motion::Left))
|
||||||
orbclient::K_UP if event.pressed => editor.action(Action::Up),
|
}
|
||||||
orbclient::K_DOWN if event.pressed => editor.action(Action::Down),
|
orbclient::K_RIGHT if event.pressed => {
|
||||||
orbclient::K_HOME if event.pressed => editor.action(Action::Home),
|
editor.action(Action::Motion(Motion::Right))
|
||||||
orbclient::K_END if event.pressed => editor.action(Action::End),
|
}
|
||||||
orbclient::K_PGUP if event.pressed => editor.action(Action::PageUp),
|
orbclient::K_UP if event.pressed => editor.action(Action::Motion(Motion::Up)),
|
||||||
orbclient::K_PGDN if event.pressed => editor.action(Action::PageDown),
|
orbclient::K_DOWN if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::Down))
|
||||||
|
}
|
||||||
|
orbclient::K_HOME if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::Home))
|
||||||
|
}
|
||||||
|
orbclient::K_END if event.pressed => editor.action(Action::Motion(Motion::End)),
|
||||||
|
orbclient::K_PGUP if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::PageUp))
|
||||||
|
}
|
||||||
|
orbclient::K_PGDN if event.pressed => {
|
||||||
|
editor.action(Action::Motion(Motion::PageDown))
|
||||||
|
}
|
||||||
orbclient::K_ENTER if event.pressed => editor.action(Action::Enter),
|
orbclient::K_ENTER if event.pressed => editor.action(Action::Enter),
|
||||||
orbclient::K_BKSP if event.pressed => editor.action(Action::Backspace),
|
orbclient::K_BKSP if event.pressed => editor.action(Action::Backspace),
|
||||||
orbclient::K_DEL if event.pressed => editor.action(Action::Delete),
|
orbclient::K_DEL if event.pressed => editor.action(Action::Delete),
|
||||||
|
|
|
||||||
393
src/buffer.rs
393
src/buffer.rs
|
|
@ -6,102 +6,11 @@ use core::{cmp, fmt};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, FontSystem,
|
Affinity, Attrs, AttrsList, BidiParagraphs, BorrowedWithFontSystem, BufferLine, Color, Cursor,
|
||||||
LayoutGlyph, LayoutLine, ShapeBuffer, ShapeLine, Shaping, Wrap,
|
FontSystem, LayoutCursor, LayoutGlyph, LayoutLine, Motion, ShapeBuffer, ShapeLine, Shaping,
|
||||||
|
Wrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Current cursor location
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub struct Cursor {
|
|
||||||
/// Text line the cursor is on
|
|
||||||
pub line: usize,
|
|
||||||
/// First-byte-index of glyph at cursor (will insert behind this glyph)
|
|
||||||
pub index: usize,
|
|
||||||
/// Whether to associate the cursor with the run before it or the run after it if placed at the
|
|
||||||
/// boundary between two runs
|
|
||||||
pub affinity: Affinity,
|
|
||||||
/// Cursor color
|
|
||||||
pub color: Option<Color>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cursor {
|
|
||||||
/// Create a new cursor
|
|
||||||
pub const fn new(line: usize, index: usize) -> Self {
|
|
||||||
Self::new_with_affinity(line, index, Affinity::Before)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new cursor, specifying the affinity
|
|
||||||
pub const fn new_with_affinity(line: usize, index: usize, affinity: Affinity) -> Self {
|
|
||||||
Self {
|
|
||||||
line,
|
|
||||||
index,
|
|
||||||
affinity,
|
|
||||||
color: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Create a new cursor, specifying the color
|
|
||||||
pub const fn new_with_color(line: usize, index: usize, color: Color) -> Self {
|
|
||||||
Self {
|
|
||||||
line,
|
|
||||||
index,
|
|
||||||
affinity: Affinity::Before,
|
|
||||||
color: Some(color),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether to associate cursors placed at a boundary between runs with the run before or after it.
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub enum Affinity {
|
|
||||||
#[default]
|
|
||||||
Before,
|
|
||||||
After,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Affinity {
|
|
||||||
pub fn before(&self) -> bool {
|
|
||||||
*self == Self::Before
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn after(&self) -> bool {
|
|
||||||
*self == Self::After
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_before(before: bool) -> Self {
|
|
||||||
if before {
|
|
||||||
Self::Before
|
|
||||||
} else {
|
|
||||||
Self::After
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_after(after: bool) -> Self {
|
|
||||||
if after {
|
|
||||||
Self::After
|
|
||||||
} else {
|
|
||||||
Self::Before
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The position of a cursor within a [`Buffer`].
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct LayoutCursor {
|
|
||||||
pub line: usize,
|
|
||||||
pub layout: usize,
|
|
||||||
pub glyph: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutCursor {
|
|
||||||
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
line,
|
|
||||||
layout,
|
|
||||||
glyph,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A line of visible text for rendering
|
/// A line of visible text for rendering
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LayoutRun<'a> {
|
pub struct LayoutRun<'a> {
|
||||||
|
|
@ -848,6 +757,297 @@ impl Buffer {
|
||||||
new_cursor_opt
|
new_cursor_opt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a [`Motion`] to a [`Cursor`]
|
||||||
|
pub fn cursor_motion(
|
||||||
|
&mut self,
|
||||||
|
font_system: &mut FontSystem,
|
||||||
|
mut cursor: Cursor,
|
||||||
|
motion: Motion,
|
||||||
|
) -> Option<Cursor> {
|
||||||
|
match motion {
|
||||||
|
Motion::LayoutCursor(layout_cursor) => {
|
||||||
|
let layout = self.line_layout(font_system, layout_cursor.line)?;
|
||||||
|
|
||||||
|
let layout_line = match layout.get(layout_cursor.layout) {
|
||||||
|
Some(some) => some,
|
||||||
|
None => match layout.last() {
|
||||||
|
Some(some) => some,
|
||||||
|
None => return None,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let (new_index, new_affinity) = match layout_line.glyphs.get(layout_cursor.glyph) {
|
||||||
|
Some(glyph) => (glyph.start, Affinity::After),
|
||||||
|
None => match layout_line.glyphs.last() {
|
||||||
|
Some(glyph) => (glyph.end, Affinity::Before),
|
||||||
|
//TODO: is this correct?
|
||||||
|
None => (0, Affinity::After),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if cursor.line != layout_cursor.line
|
||||||
|
|| cursor.index != new_index
|
||||||
|
|| cursor.affinity != new_affinity
|
||||||
|
{
|
||||||
|
cursor.line = layout_cursor.line;
|
||||||
|
cursor.index = new_index;
|
||||||
|
cursor.affinity = new_affinity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::Previous => {
|
||||||
|
let line = self.lines.get(cursor.line)?;
|
||||||
|
if cursor.index > 0 {
|
||||||
|
// Find previous character index
|
||||||
|
let mut prev_index = 0;
|
||||||
|
for (i, _) in line.text().grapheme_indices(true) {
|
||||||
|
if i < cursor.index {
|
||||||
|
prev_index = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.index = prev_index;
|
||||||
|
cursor.affinity = Affinity::After;
|
||||||
|
} else if cursor.line > 0 {
|
||||||
|
cursor.line -= 1;
|
||||||
|
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||||
|
cursor.affinity = Affinity::After;
|
||||||
|
}
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::Next => {
|
||||||
|
let line = self.lines.get(cursor.line)?;
|
||||||
|
if cursor.index < line.text().len() {
|
||||||
|
for (i, c) in line.text().grapheme_indices(true) {
|
||||||
|
if i == cursor.index {
|
||||||
|
cursor.index += c.len();
|
||||||
|
cursor.affinity = Affinity::Before;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if cursor.line + 1 < self.lines.len() {
|
||||||
|
cursor.line += 1;
|
||||||
|
cursor.index = 0;
|
||||||
|
cursor.affinity = Affinity::Before;
|
||||||
|
}
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::Left => {
|
||||||
|
let rtl_opt = self
|
||||||
|
.line_shape(font_system, cursor.line)
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
|
if let Some(rtl) = rtl_opt {
|
||||||
|
if rtl {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
|
||||||
|
} else {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::Right => {
|
||||||
|
let rtl_opt = self
|
||||||
|
.line_shape(font_system, cursor.line)
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
|
if let Some(rtl) = rtl_opt {
|
||||||
|
if rtl {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
|
||||||
|
} else {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::Up => {
|
||||||
|
//TODO: make this preserve X as best as possible!
|
||||||
|
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
|
||||||
|
if cursor.x_opt.is_none() {
|
||||||
|
cursor.x_opt = Some(
|
||||||
|
layout_cursor.glyph as i32, //TODO: glyph x position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout_cursor.layout > 0 {
|
||||||
|
layout_cursor.layout -= 1;
|
||||||
|
} else if layout_cursor.line > 0 {
|
||||||
|
layout_cursor.line -= 1;
|
||||||
|
layout_cursor.layout = usize::max_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cursor_x) = cursor.x_opt {
|
||||||
|
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor =
|
||||||
|
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||||
|
}
|
||||||
|
Motion::Down => {
|
||||||
|
//TODO: make this preserve X as best as possible!
|
||||||
|
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
|
||||||
|
let layout_len = self
|
||||||
|
.line_layout(font_system, layout_cursor.line)
|
||||||
|
.expect("layout not found")
|
||||||
|
.len();
|
||||||
|
|
||||||
|
if cursor.x_opt.is_none() {
|
||||||
|
cursor.x_opt = Some(
|
||||||
|
layout_cursor.glyph as i32, //TODO: glyph x position
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if layout_cursor.layout + 1 < layout_len {
|
||||||
|
layout_cursor.layout += 1;
|
||||||
|
} else if layout_cursor.line + 1 < self.lines.len() {
|
||||||
|
layout_cursor.line += 1;
|
||||||
|
layout_cursor.layout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cursor_x) = cursor.x_opt {
|
||||||
|
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor =
|
||||||
|
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||||
|
}
|
||||||
|
Motion::Home => {
|
||||||
|
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
layout_cursor.glyph = 0;
|
||||||
|
cursor =
|
||||||
|
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::SoftHome => {
|
||||||
|
let line = self.lines.get(cursor.line)?;
|
||||||
|
cursor.index = line
|
||||||
|
.text()
|
||||||
|
.char_indices()
|
||||||
|
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
|
||||||
|
.next()
|
||||||
|
.unwrap_or(0);
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::End => {
|
||||||
|
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
layout_cursor.glyph = usize::max_value();
|
||||||
|
cursor =
|
||||||
|
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::ParagraphStart => {
|
||||||
|
cursor.index = 0;
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::ParagraphEnd => {
|
||||||
|
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::PageUp => {
|
||||||
|
cursor = self.cursor_motion(
|
||||||
|
font_system,
|
||||||
|
cursor,
|
||||||
|
Motion::Vertical(-self.size().1 as i32),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Motion::PageDown => {
|
||||||
|
cursor = self.cursor_motion(
|
||||||
|
font_system,
|
||||||
|
cursor,
|
||||||
|
Motion::Vertical(self.size().1 as i32),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Motion::Vertical(px) => {
|
||||||
|
// TODO more efficient
|
||||||
|
let lines = px / self.metrics().line_height as i32;
|
||||||
|
match lines.cmp(&0) {
|
||||||
|
cmp::Ordering::Less => {
|
||||||
|
for _ in 0..-lines {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Up)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmp::Ordering::Greater => {
|
||||||
|
for _ in 0..lines {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::Down)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmp::Ordering::Equal => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::PreviousWord => {
|
||||||
|
let line = self.lines.get(cursor.line)?;
|
||||||
|
if cursor.index > 0 {
|
||||||
|
cursor.index = line
|
||||||
|
.text()
|
||||||
|
.unicode_word_indices()
|
||||||
|
.rev()
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.find(|&i| i < cursor.index)
|
||||||
|
.unwrap_or(0);
|
||||||
|
} else if cursor.line > 0 {
|
||||||
|
cursor.line -= 1;
|
||||||
|
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||||
|
}
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::NextWord => {
|
||||||
|
let line = self.lines.get(cursor.line)?;
|
||||||
|
if cursor.index < line.text().len() {
|
||||||
|
cursor.index = line
|
||||||
|
.text()
|
||||||
|
.unicode_word_indices()
|
||||||
|
.map(|(i, word)| i + word.len())
|
||||||
|
.find(|&i| i > cursor.index)
|
||||||
|
.unwrap_or(line.text().len());
|
||||||
|
} else if cursor.line + 1 < self.lines.len() {
|
||||||
|
cursor.line += 1;
|
||||||
|
cursor.index = 0;
|
||||||
|
}
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::LeftWord => {
|
||||||
|
let rtl_opt = self
|
||||||
|
.line_shape(font_system, cursor.line)
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
|
if let Some(rtl) = rtl_opt {
|
||||||
|
if rtl {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
|
||||||
|
} else {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::RightWord => {
|
||||||
|
let rtl_opt = self
|
||||||
|
.line_shape(font_system, cursor.line)
|
||||||
|
.map(|shape| shape.rtl);
|
||||||
|
if let Some(rtl) = rtl_opt {
|
||||||
|
if rtl {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
|
||||||
|
} else {
|
||||||
|
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Motion::BufferStart => {
|
||||||
|
cursor.line = 0;
|
||||||
|
cursor.index = 0;
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::BufferEnd => {
|
||||||
|
cursor.line = self.lines.len() - 1;
|
||||||
|
cursor.index = self.lines.get(cursor.line)?.text().len();
|
||||||
|
cursor.x_opt = None;
|
||||||
|
}
|
||||||
|
Motion::GotoLine(line) => {
|
||||||
|
let mut layout_cursor = self.layout_cursor(&cursor);
|
||||||
|
layout_cursor.line = line;
|
||||||
|
cursor =
|
||||||
|
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(cursor)
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the buffer
|
/// Draw the buffer
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(
|
pub fn draw<F>(
|
||||||
|
|
@ -972,6 +1172,11 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> {
|
||||||
.set_rich_text(self.font_system, spans, default_attrs, shaping);
|
.set_rich_text(self.font_system, spans, default_attrs, shaping);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a [`Motion`] to a [`Cursor`]
|
||||||
|
pub fn cursor_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
|
||||||
|
self.inner.cursor_motion(self.font_system, cursor, motion)
|
||||||
|
}
|
||||||
|
|
||||||
/// Draw the buffer
|
/// Draw the buffer
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
|
||||||
|
|
|
||||||
147
src/cursor.rs
Normal file
147
src/cursor.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
use crate::Color;
|
||||||
|
|
||||||
|
/// Current cursor location
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub struct Cursor {
|
||||||
|
/// Text line the cursor is on
|
||||||
|
pub line: usize,
|
||||||
|
/// First-byte-index of glyph at cursor (will insert behind this glyph)
|
||||||
|
pub index: usize,
|
||||||
|
/// Whether to associate the cursor with the run before it or the run after it if placed at the
|
||||||
|
/// boundary between two runs
|
||||||
|
pub affinity: Affinity,
|
||||||
|
/// Cached X position used for up and down movement
|
||||||
|
pub x_opt: Option<i32>,
|
||||||
|
/// Cursor color
|
||||||
|
pub color: Option<Color>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
/// Create a new cursor
|
||||||
|
pub const fn new(line: usize, index: usize) -> Self {
|
||||||
|
Self::new_with_affinity(line, index, Affinity::Before)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new cursor, specifying the affinity
|
||||||
|
pub const fn new_with_affinity(line: usize, index: usize, affinity: Affinity) -> Self {
|
||||||
|
Self {
|
||||||
|
line,
|
||||||
|
index,
|
||||||
|
affinity,
|
||||||
|
x_opt: None,
|
||||||
|
color: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Create a new cursor, specifying the color
|
||||||
|
pub const fn new_with_color(line: usize, index: usize, color: Color) -> Self {
|
||||||
|
Self {
|
||||||
|
line,
|
||||||
|
index,
|
||||||
|
affinity: Affinity::Before,
|
||||||
|
x_opt: None,
|
||||||
|
color: Some(color),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether to associate cursors placed at a boundary between runs with the run before or after it.
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum Affinity {
|
||||||
|
#[default]
|
||||||
|
Before,
|
||||||
|
After,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Affinity {
|
||||||
|
pub fn before(&self) -> bool {
|
||||||
|
*self == Self::Before
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn after(&self) -> bool {
|
||||||
|
*self == Self::After
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_before(before: bool) -> Self {
|
||||||
|
if before {
|
||||||
|
Self::Before
|
||||||
|
} else {
|
||||||
|
Self::After
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_after(after: bool) -> Self {
|
||||||
|
if after {
|
||||||
|
Self::After
|
||||||
|
} else {
|
||||||
|
Self::Before
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The position of a cursor within a [`Buffer`].
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct LayoutCursor {
|
||||||
|
pub line: usize,
|
||||||
|
pub layout: usize,
|
||||||
|
pub glyph: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutCursor {
|
||||||
|
/// Create a new [`LayoutCursor`]
|
||||||
|
pub fn new(line: usize, layout: usize, glyph: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
line,
|
||||||
|
layout,
|
||||||
|
glyph,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A motion to perform on a [`Cursor`]
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Motion {
|
||||||
|
/// Apply specific [`LayoutCursor`]
|
||||||
|
LayoutCursor(LayoutCursor),
|
||||||
|
/// Move cursor to previous character ([Self::Left] in LTR, [Self::Right] in RTL)
|
||||||
|
Previous,
|
||||||
|
/// Move cursor to next character ([Self::Right] in LTR, [Self::Left] in RTL)
|
||||||
|
Next,
|
||||||
|
/// Move cursor left
|
||||||
|
Left,
|
||||||
|
/// Move cursor right
|
||||||
|
Right,
|
||||||
|
/// Move cursor up
|
||||||
|
Up,
|
||||||
|
/// Move cursor down
|
||||||
|
Down,
|
||||||
|
/// Move cursor to start of line
|
||||||
|
Home,
|
||||||
|
/// Move cursor to start of line, skipping whitespace
|
||||||
|
SoftHome,
|
||||||
|
/// Move cursor to end of line
|
||||||
|
End,
|
||||||
|
/// Move cursor to start of paragraph
|
||||||
|
ParagraphStart,
|
||||||
|
/// Move cursor to end of paragraph
|
||||||
|
ParagraphEnd,
|
||||||
|
/// Move cursor up one page
|
||||||
|
PageUp,
|
||||||
|
/// Move cursor down one page
|
||||||
|
PageDown,
|
||||||
|
/// Move cursor up or down by a number of pixels
|
||||||
|
Vertical(i32),
|
||||||
|
/// Move cursor to previous word boundary
|
||||||
|
PreviousWord,
|
||||||
|
/// Move cursor to next word boundary
|
||||||
|
NextWord,
|
||||||
|
/// Move cursor to next word boundary to the left
|
||||||
|
LeftWord,
|
||||||
|
/// Move cursor to next word boundary to the right
|
||||||
|
RightWord,
|
||||||
|
/// Move cursor to the start of the document
|
||||||
|
BufferStart,
|
||||||
|
/// Move cursor to the end of the document
|
||||||
|
BufferEnd,
|
||||||
|
/// Move cursor to specific line
|
||||||
|
GotoLine(usize),
|
||||||
|
}
|
||||||
|
|
@ -8,8 +8,8 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, Affinity, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem,
|
Action, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, Selection,
|
||||||
LayoutCursor, Selection, Shaping,
|
Shaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A wrapper of [`Buffer`] for easy editing
|
/// A wrapper of [`Buffer`] for easy editing
|
||||||
|
|
@ -39,40 +39,6 @@ impl Editor {
|
||||||
change: None,
|
change: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_layout_cursor(&mut self, font_system: &mut FontSystem, cursor: LayoutCursor) {
|
|
||||||
let layout = self
|
|
||||||
.buffer
|
|
||||||
.line_layout(font_system, cursor.line)
|
|
||||||
.expect("layout not found");
|
|
||||||
|
|
||||||
let layout_line = match layout.get(cursor.layout) {
|
|
||||||
Some(some) => some,
|
|
||||||
None => match layout.last() {
|
|
||||||
Some(some) => some,
|
|
||||||
None => todo!("layout cursor in line with no layouts"),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (new_index, new_affinity) = match layout_line.glyphs.get(cursor.glyph) {
|
|
||||||
Some(glyph) => (glyph.start, Affinity::After),
|
|
||||||
None => match layout_line.glyphs.last() {
|
|
||||||
Some(glyph) => (glyph.end, Affinity::Before),
|
|
||||||
//TODO: is this correct?
|
|
||||||
None => (0, Affinity::After),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.cursor.line != cursor.line
|
|
||||||
|| self.cursor.index != new_index
|
|
||||||
|| self.cursor.affinity != new_affinity
|
|
||||||
{
|
|
||||||
self.cursor.line = cursor.line;
|
|
||||||
self.cursor.index = new_index;
|
|
||||||
self.cursor.affinity = new_affinity;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Edit for Editor {
|
impl Edit for Editor {
|
||||||
|
|
@ -377,181 +343,11 @@ impl Edit for Editor {
|
||||||
let old_cursor = self.cursor;
|
let old_cursor = self.cursor;
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
Action::Previous => {
|
Action::Motion(motion) => {
|
||||||
let line = &self.buffer.lines[self.cursor.line];
|
if let Some(new_cursor) =
|
||||||
if self.cursor.index > 0 {
|
self.buffer.cursor_motion(font_system, self.cursor, motion)
|
||||||
// Find previous character index
|
{
|
||||||
let mut prev_index = 0;
|
self.cursor = new_cursor;
|
||||||
for (i, _) in line.text().grapheme_indices(true) {
|
|
||||||
if i < self.cursor.index {
|
|
||||||
prev_index = i;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cursor.index = prev_index;
|
|
||||||
self.cursor.affinity = Affinity::After;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
} else if self.cursor.line > 0 {
|
|
||||||
self.cursor.line -= 1;
|
|
||||||
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
|
||||||
self.cursor.affinity = Affinity::After;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::Next => {
|
|
||||||
let line = &self.buffer.lines[self.cursor.line];
|
|
||||||
if self.cursor.index < line.text().len() {
|
|
||||||
for (i, c) in line.text().grapheme_indices(true) {
|
|
||||||
if i == self.cursor.index {
|
|
||||||
self.cursor.index += c.len();
|
|
||||||
self.cursor.affinity = Affinity::Before;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if self.cursor.line + 1 < self.buffer.lines.len() {
|
|
||||||
self.cursor.line += 1;
|
|
||||||
self.cursor.index = 0;
|
|
||||||
self.cursor.affinity = Affinity::Before;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::Left => {
|
|
||||||
let rtl_opt = self
|
|
||||||
.buffer
|
|
||||||
.line_shape(font_system, self.cursor.line)
|
|
||||||
.map(|shape| shape.rtl);
|
|
||||||
if let Some(rtl) = rtl_opt {
|
|
||||||
if rtl {
|
|
||||||
self.action(font_system, Action::Next);
|
|
||||||
} else {
|
|
||||||
self.action(font_system, Action::Previous);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Right => {
|
|
||||||
let rtl_opt = self
|
|
||||||
.buffer
|
|
||||||
.line_shape(font_system, self.cursor.line)
|
|
||||||
.map(|shape| shape.rtl);
|
|
||||||
if let Some(rtl) = rtl_opt {
|
|
||||||
if rtl {
|
|
||||||
self.action(font_system, Action::Previous);
|
|
||||||
} else {
|
|
||||||
self.action(font_system, Action::Next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::Up => {
|
|
||||||
//TODO: make this preserve X as best as possible!
|
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
|
||||||
|
|
||||||
if self.cursor_x_opt.is_none() {
|
|
||||||
self.cursor_x_opt = Some(
|
|
||||||
cursor.glyph as i32, //TODO: glyph x position
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor.layout > 0 {
|
|
||||||
cursor.layout -= 1;
|
|
||||||
} else if cursor.line > 0 {
|
|
||||||
cursor.line -= 1;
|
|
||||||
cursor.layout = usize::max_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cursor_x) = self.cursor_x_opt {
|
|
||||||
cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_layout_cursor(font_system, cursor);
|
|
||||||
}
|
|
||||||
Action::Down => {
|
|
||||||
//TODO: make this preserve X as best as possible!
|
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
|
||||||
|
|
||||||
let layout_len = self
|
|
||||||
.buffer
|
|
||||||
.line_layout(font_system, cursor.line)
|
|
||||||
.expect("layout not found")
|
|
||||||
.len();
|
|
||||||
|
|
||||||
if self.cursor_x_opt.is_none() {
|
|
||||||
self.cursor_x_opt = Some(
|
|
||||||
cursor.glyph as i32, //TODO: glyph x position
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if cursor.layout + 1 < layout_len {
|
|
||||||
cursor.layout += 1;
|
|
||||||
} else if cursor.line + 1 < self.buffer.lines.len() {
|
|
||||||
cursor.line += 1;
|
|
||||||
cursor.layout = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cursor_x) = self.cursor_x_opt {
|
|
||||||
cursor.glyph = cursor_x as usize; //TODO: glyph x position
|
|
||||||
}
|
|
||||||
|
|
||||||
self.set_layout_cursor(font_system, cursor);
|
|
||||||
}
|
|
||||||
Action::Home => {
|
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
|
||||||
cursor.glyph = 0;
|
|
||||||
self.set_layout_cursor(font_system, cursor);
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::SoftHome => {
|
|
||||||
let line = &self.buffer.lines[self.cursor.line];
|
|
||||||
self.cursor.index = line
|
|
||||||
.text()
|
|
||||||
.char_indices()
|
|
||||||
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
|
|
||||||
.next()
|
|
||||||
.unwrap_or(0);
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::End => {
|
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
|
||||||
cursor.glyph = usize::max_value();
|
|
||||||
self.set_layout_cursor(font_system, cursor);
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::ParagraphStart => {
|
|
||||||
self.cursor.index = 0;
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
Action::ParagraphEnd => {
|
|
||||||
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
Action::PageUp => {
|
|
||||||
self.action(font_system, Action::Vertical(-self.buffer.size().1 as i32));
|
|
||||||
}
|
|
||||||
Action::PageDown => {
|
|
||||||
self.action(font_system, Action::Vertical(self.buffer.size().1 as i32));
|
|
||||||
}
|
|
||||||
Action::Vertical(px) => {
|
|
||||||
// TODO more efficient
|
|
||||||
let lines = px / self.buffer.metrics().line_height as i32;
|
|
||||||
match lines.cmp(&0) {
|
|
||||||
cmp::Ordering::Less => {
|
|
||||||
for _ in 0..-lines {
|
|
||||||
self.action(font_system, Action::Up);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmp::Ordering::Greater => {
|
|
||||||
for _ in 0..lines {
|
|
||||||
self.action(font_system, Action::Down);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cmp::Ordering::Equal => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Action::Escape => {
|
Action::Escape => {
|
||||||
|
|
@ -837,88 +633,11 @@ impl Edit for Editor {
|
||||||
scroll += lines;
|
scroll += lines;
|
||||||
self.buffer.set_scroll(scroll);
|
self.buffer.set_scroll(scroll);
|
||||||
}
|
}
|
||||||
Action::PreviousWord => {
|
|
||||||
let line = &self.buffer.lines[self.cursor.line];
|
|
||||||
if self.cursor.index > 0 {
|
|
||||||
self.cursor.index = line
|
|
||||||
.text()
|
|
||||||
.unicode_word_indices()
|
|
||||||
.rev()
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.find(|&i| i < self.cursor.index)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
} else if self.cursor.line > 0 {
|
|
||||||
self.cursor.line -= 1;
|
|
||||||
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::NextWord => {
|
|
||||||
let line = &self.buffer.lines[self.cursor.line];
|
|
||||||
if self.cursor.index < line.text().len() {
|
|
||||||
self.cursor.index = line
|
|
||||||
.text()
|
|
||||||
.unicode_word_indices()
|
|
||||||
.map(|(i, word)| i + word.len())
|
|
||||||
.find(|&i| i > self.cursor.index)
|
|
||||||
.unwrap_or(line.text().len());
|
|
||||||
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
} else if self.cursor.line + 1 < self.buffer.lines.len() {
|
|
||||||
self.cursor.line += 1;
|
|
||||||
self.cursor.index = 0;
|
|
||||||
self.buffer.set_redraw(true);
|
|
||||||
}
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::LeftWord => {
|
|
||||||
let rtl_opt = self
|
|
||||||
.buffer
|
|
||||||
.line_shape(font_system, self.cursor.line)
|
|
||||||
.map(|shape| shape.rtl);
|
|
||||||
if let Some(rtl) = rtl_opt {
|
|
||||||
if rtl {
|
|
||||||
self.action(font_system, Action::NextWord);
|
|
||||||
} else {
|
|
||||||
self.action(font_system, Action::PreviousWord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::RightWord => {
|
|
||||||
let rtl_opt = self
|
|
||||||
.buffer
|
|
||||||
.line_shape(font_system, self.cursor.line)
|
|
||||||
.map(|shape| shape.rtl);
|
|
||||||
if let Some(rtl) = rtl_opt {
|
|
||||||
if rtl {
|
|
||||||
self.action(font_system, Action::PreviousWord);
|
|
||||||
} else {
|
|
||||||
self.action(font_system, Action::NextWord);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Action::BufferStart => {
|
|
||||||
self.cursor.line = 0;
|
|
||||||
self.cursor.index = 0;
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::BufferEnd => {
|
|
||||||
self.cursor.line = self.buffer.lines.len() - 1;
|
|
||||||
self.cursor.index = self.buffer.lines[self.cursor.line].text().len();
|
|
||||||
self.cursor_x_opt = None;
|
|
||||||
}
|
|
||||||
Action::GotoLine(line) => {
|
|
||||||
let mut cursor = self.buffer.layout_cursor(&self.cursor);
|
|
||||||
cursor.line = line;
|
|
||||||
self.set_layout_cursor(font_system, cursor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if old_cursor != self.cursor {
|
if old_cursor != self.cursor {
|
||||||
self.cursor_moved = true;
|
self.cursor_moved = true;
|
||||||
|
self.buffer.set_redraw(true);
|
||||||
|
|
||||||
/*TODO
|
/*TODO
|
||||||
if let Some(glyph) = run.glyphs.get(new_cursor_glyph) {
|
if let Some(glyph) = run.glyphs.get(new_cursor_glyph) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
#[cfg(feature = "swash")]
|
#[cfg(feature = "swash")]
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem};
|
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
|
||||||
|
|
||||||
pub use self::editor::*;
|
pub use self::editor::*;
|
||||||
mod editor;
|
mod editor;
|
||||||
|
|
@ -23,34 +23,8 @@ mod vi;
|
||||||
/// An action to perform on an [`Editor`]
|
/// An action to perform on an [`Editor`]
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
/// Move cursor to previous character ([Self::Left] in LTR, [Self::Right] in RTL)
|
/// Move the cursor with some motion
|
||||||
Previous,
|
Motion(Motion),
|
||||||
/// Move cursor to next character ([Self::Right] in LTR, [Self::Left] in RTL)
|
|
||||||
Next,
|
|
||||||
/// Move cursor left
|
|
||||||
Left,
|
|
||||||
/// Move cursor right
|
|
||||||
Right,
|
|
||||||
/// Move cursor up
|
|
||||||
Up,
|
|
||||||
/// Move cursor down
|
|
||||||
Down,
|
|
||||||
/// Move cursor to start of line
|
|
||||||
Home,
|
|
||||||
/// Move cursor to start of line, skipping whitespace
|
|
||||||
SoftHome,
|
|
||||||
/// Move cursor to end of line
|
|
||||||
End,
|
|
||||||
/// Move cursor to start of paragraph
|
|
||||||
ParagraphStart,
|
|
||||||
/// Move cursor to end of paragraph
|
|
||||||
ParagraphEnd,
|
|
||||||
/// Move cursor up one page
|
|
||||||
PageUp,
|
|
||||||
/// Move cursor down one page
|
|
||||||
PageDown,
|
|
||||||
/// Move cursor up or down by a number of pixels
|
|
||||||
Vertical(i32),
|
|
||||||
/// Escape, clears selection
|
/// Escape, clears selection
|
||||||
Escape,
|
Escape,
|
||||||
/// Insert character at cursor
|
/// Insert character at cursor
|
||||||
|
|
@ -89,20 +63,6 @@ pub enum Action {
|
||||||
Scroll {
|
Scroll {
|
||||||
lines: i32,
|
lines: i32,
|
||||||
},
|
},
|
||||||
/// Move cursor to previous word boundary
|
|
||||||
PreviousWord,
|
|
||||||
/// Move cursor to next word boundary
|
|
||||||
NextWord,
|
|
||||||
/// Move cursor to next word boundary to the left
|
|
||||||
LeftWord,
|
|
||||||
/// Move cursor to next word boundary to the right
|
|
||||||
RightWord,
|
|
||||||
/// Move cursor to the start of the document
|
|
||||||
BufferStart,
|
|
||||||
/// Move cursor to the end of the document
|
|
||||||
BufferEnd,
|
|
||||||
/// Move cursor to specific line
|
|
||||||
GotoLine(usize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A unique change to an editor
|
/// A unique change to an editor
|
||||||
|
|
|
||||||
100
src/edit/vi.rs
100
src/edit/vi.rs
|
|
@ -1,11 +1,11 @@
|
||||||
use alloc::{collections::BTreeMap, string::String};
|
use alloc::{collections::BTreeMap, string::String};
|
||||||
use core::cmp;
|
use core::cmp;
|
||||||
use modit::{Event, Key, Motion, Parser, TextObject, WordIter};
|
use modit::{Event, Key, Parser, TextObject, WordIter};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem,
|
Action, AttrsList, BorrowedWithFontSystem, Buffer, Change, Color, Cursor, Edit, FontSystem,
|
||||||
Selection, SyntaxEditor, SyntaxTheme,
|
Motion, Selection, SyntaxEditor, SyntaxTheme,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use modit::{ViMode, ViParser};
|
pub use modit::{ViMode, ViParser};
|
||||||
|
|
@ -342,19 +342,19 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
//TODO: this leaves lots of room for issues in translation, should we directly accept Key?
|
//TODO: this leaves lots of room for issues in translation, should we directly accept Key?
|
||||||
Action::Backspace => Key::Backspace,
|
Action::Backspace => Key::Backspace,
|
||||||
Action::Delete => Key::Delete,
|
Action::Delete => Key::Delete,
|
||||||
Action::Down => Key::Down,
|
Action::Motion(Motion::Down) => Key::Down,
|
||||||
Action::End => Key::End,
|
Action::Motion(Motion::End) => Key::End,
|
||||||
Action::Enter => Key::Enter,
|
Action::Enter => Key::Enter,
|
||||||
Action::Escape => Key::Escape,
|
Action::Escape => Key::Escape,
|
||||||
Action::Home => Key::Home,
|
Action::Motion(Motion::Home) => Key::Home,
|
||||||
Action::Indent => Key::Tab,
|
Action::Indent => Key::Tab,
|
||||||
Action::Insert(c) => Key::Char(c),
|
Action::Insert(c) => Key::Char(c),
|
||||||
Action::Left => Key::Left,
|
Action::Motion(Motion::Left) => Key::Left,
|
||||||
Action::PageDown => Key::PageDown,
|
Action::Motion(Motion::PageDown) => Key::PageDown,
|
||||||
Action::PageUp => Key::PageUp,
|
Action::Motion(Motion::PageUp) => Key::PageUp,
|
||||||
Action::Right => Key::Right,
|
Action::Motion(Motion::Right) => Key::Right,
|
||||||
Action::Unindent => Key::Backtab,
|
Action::Unindent => Key::Backtab,
|
||||||
Action::Up => Key::Up,
|
Action::Motion(Motion::Up) => Key::Up,
|
||||||
_ => {
|
_ => {
|
||||||
log::debug!("Pass through action {:?}", action);
|
log::debug!("Pass through action {:?}", action);
|
||||||
editor.action(font_system, action);
|
editor.action(font_system, action);
|
||||||
|
|
@ -448,9 +448,9 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
|
|
||||||
// Move to inserted line, preserving cursor x position
|
// Move to inserted line, preserving cursor x position
|
||||||
if after {
|
if after {
|
||||||
editor.action(font_system, Action::Down);
|
editor.action(font_system, Action::Motion(Motion::Down));
|
||||||
} else {
|
} else {
|
||||||
editor.action(font_system, Action::Up);
|
editor.action(font_system, Action::Motion(Motion::Up));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -550,35 +550,37 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
}
|
}
|
||||||
Event::Motion(motion) => {
|
Event::Motion(motion) => {
|
||||||
match motion {
|
match motion {
|
||||||
Motion::Around => {
|
modit::Motion::Around => {
|
||||||
//TODO: what to do for this psuedo-motion?
|
//TODO: what to do for this psuedo-motion?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::Down => Action::Down,
|
modit::Motion::Down => Action::Motion(Motion::Down),
|
||||||
Motion::End => Action::End,
|
modit::Motion::End => Action::Motion(Motion::End),
|
||||||
Motion::GotoLine(line) => Action::GotoLine(line.saturating_sub(1)),
|
modit::Motion::GotoLine(line) => {
|
||||||
Motion::GotoEof => {
|
Action::Motion(Motion::GotoLine(line.saturating_sub(1)))
|
||||||
Action::GotoLine(editor.buffer().lines.len().saturating_sub(1))
|
|
||||||
}
|
}
|
||||||
Motion::Home => Action::Home,
|
modit::Motion::GotoEof => Action::Motion(Motion::GotoLine(
|
||||||
Motion::Inside => {
|
editor.buffer().lines.len().saturating_sub(1),
|
||||||
|
)),
|
||||||
|
modit::Motion::Home => Action::Motion(Motion::Home),
|
||||||
|
modit::Motion::Inside => {
|
||||||
//TODO: what to do for this psuedo-motion?
|
//TODO: what to do for this psuedo-motion?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::Left => Action::Left,
|
modit::Motion::Left => Action::Motion(Motion::Left),
|
||||||
Motion::LeftInLine => {
|
modit::Motion::LeftInLine => {
|
||||||
let cursor = editor.cursor();
|
let cursor = editor.cursor();
|
||||||
if cursor.index > 0 {
|
if cursor.index > 0 {
|
||||||
Action::Left
|
Action::Motion(Motion::Left)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::Line => {
|
modit::Motion::Line => {
|
||||||
//TODO: what to do for this psuedo-motion?
|
//TODO: what to do for this psuedo-motion?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::NextChar(find_c) => {
|
modit::Motion::NextChar(find_c) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
let text = buffer.lines[cursor.line].text();
|
let text = buffer.lines[cursor.line].text();
|
||||||
|
|
@ -597,7 +599,7 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::NextCharTill(find_c) => {
|
modit::Motion::NextCharTill(find_c) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
let text = buffer.lines[cursor.line].text();
|
let text = buffer.lines[cursor.line].text();
|
||||||
|
|
@ -615,14 +617,14 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::NextSearch => match &self.search_opt {
|
modit::Motion::NextSearch => match &self.search_opt {
|
||||||
Some((value, forwards)) => {
|
Some((value, forwards)) => {
|
||||||
search(editor, value, *forwards);
|
search(editor, value, *forwards);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
None => return,
|
None => return,
|
||||||
},
|
},
|
||||||
Motion::NextWordEnd(word) => {
|
modit::Motion::NextWordEnd(word) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -649,7 +651,7 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
editor.set_cursor(cursor);
|
editor.set_cursor(cursor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::NextWordStart(word) => {
|
modit::Motion::NextWordStart(word) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -674,9 +676,9 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
editor.set_cursor(cursor);
|
editor.set_cursor(cursor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::PageDown => Action::PageDown,
|
modit::Motion::PageDown => Action::Motion(Motion::PageDown),
|
||||||
Motion::PageUp => Action::PageUp,
|
modit::Motion::PageUp => Action::Motion(Motion::PageUp),
|
||||||
Motion::PreviousChar(find_c) => {
|
modit::Motion::PreviousChar(find_c) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
let text = buffer.lines[cursor.line].text();
|
let text = buffer.lines[cursor.line].text();
|
||||||
|
|
@ -695,7 +697,7 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::PreviousCharTill(find_c) => {
|
modit::Motion::PreviousCharTill(find_c) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
let text = buffer.lines[cursor.line].text();
|
let text = buffer.lines[cursor.line].text();
|
||||||
|
|
@ -722,14 +724,14 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::PreviousSearch => match &self.search_opt {
|
modit::Motion::PreviousSearch => match &self.search_opt {
|
||||||
Some((value, forwards)) => {
|
Some((value, forwards)) => {
|
||||||
search(editor, value, !*forwards);
|
search(editor, value, !*forwards);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
None => return,
|
None => return,
|
||||||
},
|
},
|
||||||
Motion::PreviousWordEnd(word) => {
|
modit::Motion::PreviousWordEnd(word) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -757,7 +759,7 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
editor.set_cursor(cursor);
|
editor.set_cursor(cursor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::PreviousWordStart(word) => {
|
modit::Motion::PreviousWordStart(word) => {
|
||||||
let mut cursor = editor.cursor();
|
let mut cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
loop {
|
loop {
|
||||||
|
|
@ -783,38 +785,40 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
editor.set_cursor(cursor);
|
editor.set_cursor(cursor);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::Right => Action::Right,
|
modit::Motion::Right => Action::Motion(Motion::Right),
|
||||||
Motion::RightInLine => {
|
modit::Motion::RightInLine => {
|
||||||
let cursor = editor.cursor();
|
let cursor = editor.cursor();
|
||||||
let buffer = editor.buffer();
|
let buffer = editor.buffer();
|
||||||
if cursor.index < buffer.lines[cursor.line].text().len() {
|
if cursor.index < buffer.lines[cursor.line].text().len() {
|
||||||
Action::Right
|
Action::Motion(Motion::Right)
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::ScreenHigh => {
|
modit::Motion::ScreenHigh => {
|
||||||
//TODO: is this efficient?
|
//TODO: is this efficient?
|
||||||
if let Some(first) = editor.buffer().layout_runs().next() {
|
if let Some(first) = editor.buffer().layout_runs().next() {
|
||||||
Action::GotoLine(first.line_i)
|
Action::Motion(Motion::GotoLine(first.line_i))
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::ScreenLow => {
|
modit::Motion::ScreenLow => {
|
||||||
//TODO: is this efficient?
|
//TODO: is this efficient?
|
||||||
if let Some(last) = editor.buffer().layout_runs().last() {
|
if let Some(last) = editor.buffer().layout_runs().last() {
|
||||||
Action::GotoLine(last.line_i)
|
Action::Motion(Motion::GotoLine(last.line_i))
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::ScreenMiddle => {
|
modit::Motion::ScreenMiddle => {
|
||||||
//TODO: is this efficient?
|
//TODO: is this efficient?
|
||||||
let mut layout_runs = editor.buffer().layout_runs();
|
let mut layout_runs = editor.buffer().layout_runs();
|
||||||
if let Some(first) = layout_runs.next() {
|
if let Some(first) = layout_runs.next() {
|
||||||
if let Some(last) = layout_runs.last() {
|
if let Some(last) = layout_runs.last() {
|
||||||
Action::GotoLine((last.line_i + first.line_i) / 2)
|
Action::Motion(Motion::GotoLine(
|
||||||
|
(last.line_i + first.line_i) / 2,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -822,12 +826,12 @@ impl<'a> Edit for ViEditor<'a> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Motion::Selection => {
|
modit::Motion::Selection => {
|
||||||
//TODO: what to do for this psuedo-motion?
|
//TODO: what to do for this psuedo-motion?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Motion::SoftHome => Action::SoftHome,
|
modit::Motion::SoftHome => Action::Motion(Motion::SoftHome),
|
||||||
Motion::Up => Action::Up,
|
modit::Motion::Up => Action::Motion(Motion::Up),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ mod buffer_line;
|
||||||
pub use self::glyph_cache::*;
|
pub use self::glyph_cache::*;
|
||||||
mod glyph_cache;
|
mod glyph_cache;
|
||||||
|
|
||||||
|
pub use self::cursor::*;
|
||||||
|
mod cursor;
|
||||||
|
|
||||||
pub use self::edit::*;
|
pub use self::edit::*;
|
||||||
mod edit;
|
mod edit;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue