implement keybinding actions
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use gpui::{App, Window};
|
use gpui::{App, AppContext, Window};
|
||||||
|
|
||||||
/// The key context used for input element keybindings.
|
/// The key context used for input element keybindings.
|
||||||
pub const DEFAULT_INPUT_CONTEXT: &str = "Input";
|
pub const DEFAULT_INPUT_CONTEXT: &str = "Input";
|
||||||
@@ -142,70 +142,94 @@ pub fn default_bindings() -> gpui::ActionBindingCollection {
|
|||||||
bindings
|
bindings
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EditableTextActionHandler {
|
pub trait EditableTextActionHandler<'app>: Sized {
|
||||||
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut App) {}
|
type Context: AppContext;
|
||||||
|
|
||||||
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut App) {}
|
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut App) {}
|
|
||||||
|
|
||||||
fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut App) {}
|
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut App) {}
|
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut App) {}
|
fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut App) {}
|
fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
|
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
}
|
||||||
fn delete_to_line_start(
|
fn delete_to_line_start(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &DeleteToBeginningOfLine,
|
_: &DeleteToBeginningOfLine,
|
||||||
_w: &mut Window,
|
_w: &mut Window,
|
||||||
_cx: &mut App,
|
_cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn delete_to_line_end(
|
||||||
|
&mut self,
|
||||||
|
_: &DeleteToEndOfLine,
|
||||||
|
_w: &mut Window,
|
||||||
|
_cx: &mut Self::Context,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
fn delete_to_line_end(&mut self, _: &DeleteToEndOfLine, _w: &mut Window, _cx: &mut App) {}
|
|
||||||
|
|
||||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut App) {}
|
fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut App) {}
|
fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut App) {}
|
fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut App) {}
|
fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut App) {}
|
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut App) {}
|
fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut App) {}
|
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut App) {}
|
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut App) {}
|
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut App) {}
|
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut App) {}
|
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut App) {}
|
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut App) {}
|
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut App) {}
|
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut App) {}
|
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut App) {}
|
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut App) {}
|
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut App) {}
|
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut App) {}
|
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
}
|
||||||
|
|
||||||
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut App) {}
|
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut App) {}
|
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut App) {}
|
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut App) {}
|
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut App) {}
|
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
fn show_character_palette(
|
fn show_character_palette(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &ShowCharacterPalette,
|
_: &ShowCharacterPalette,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
_cx: &mut App,
|
_cx: &mut Self::Context,
|
||||||
) {
|
) {
|
||||||
window.show_character_palette();
|
window.show_character_palette();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_mouse_down(
|
fn on_mouse_down(
|
||||||
&mut self,
|
&mut self,
|
||||||
_position: gpui::Point<gpui::Pixels>,
|
_event: &gpui::MouseDownEvent,
|
||||||
|
_text_position: gpui::Point<gpui::Pixels>,
|
||||||
_w: &mut Window,
|
_w: &mut Window,
|
||||||
_cx: &mut App,
|
_cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn on_mouse_up(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseUpEvent,
|
||||||
|
_w: &mut Window,
|
||||||
|
_cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn on_mouse_move(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseMoveEvent,
|
||||||
|
_text_position: gpui::Point<gpui::Pixels>,
|
||||||
|
_w: &mut Window,
|
||||||
|
_cx: &mut Self::Context,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
fn on_mouse_up(&mut self, _event: &gpui::MouseUpEvent, _w: &mut Window, _cx: &mut App) {}
|
|
||||||
fn on_mouse_move(&mut self, _event: &gpui::MouseMoveEvent, _w: &mut Window, _cx: &mut App) {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
use crate::editable_text::{TextInputStateBase, notify::TextChanged};
|
use super::notify::TextHistoryPushed;
|
||||||
|
use crate::editable_text::{
|
||||||
|
EditableTextActionHandler, TextBoundary, TextInputStateBase, TextStateNotifier,
|
||||||
|
notify::TextChanged,
|
||||||
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Bounds, Context, EntityInputHandler, EventEmitter, Pixels, Point, UTF16Selection, Window,
|
Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point,
|
||||||
|
UTF16Selection, Window,
|
||||||
};
|
};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
@@ -9,6 +14,21 @@ pub struct TextInputState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EventEmitter<TextChanged> for TextInputState {}
|
impl EventEmitter<TextChanged> for TextInputState {}
|
||||||
|
impl EventEmitter<TextHistoryPushed> for TextInputState {}
|
||||||
|
|
||||||
|
impl TextStateNotifier for Context<'_, TextInputState> {
|
||||||
|
fn notify_changed(&mut self) {
|
||||||
|
self.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_text_changed(&mut self, event: TextChanged) {
|
||||||
|
self.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_history(&mut self, event: TextHistoryPushed) {
|
||||||
|
self.emit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl EntityInputHandler for TextInputState {
|
impl EntityInputHandler for TextInputState {
|
||||||
fn text_for_range(
|
fn text_for_range(
|
||||||
@@ -52,11 +72,11 @@ impl EntityInputHandler for TextInputState {
|
|||||||
) {
|
) {
|
||||||
let range_utf8 = self.internal.ime_resolve_range(range_utf16);
|
let range_utf8 = self.internal.ime_resolve_range(range_utf16);
|
||||||
self.internal
|
self.internal
|
||||||
.replace_text_in_range_bytes(range_utf8, text_to_insert);
|
.replace_text_in_range_bytes(range_utf8, text_to_insert, cx);
|
||||||
//self.mark_layout_dirty();
|
//self.mark_layout_dirty();
|
||||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||||
cx.emit(TextChanged);
|
cx.emit_text_changed(TextChanged);
|
||||||
cx.notify();
|
cx.notify_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_and_mark_text_in_range(
|
fn replace_and_mark_text_in_range(
|
||||||
@@ -69,7 +89,7 @@ impl EntityInputHandler for TextInputState {
|
|||||||
) {
|
) {
|
||||||
let range = self.internal.ime_resolve_range(range_utf16);
|
let range = self.internal.ime_resolve_range(range_utf16);
|
||||||
self.internal
|
self.internal
|
||||||
.replace_text_in_range_bytes(range.clone(), text_to_insert);
|
.replace_text_in_range_bytes(range.clone(), text_to_insert, cx);
|
||||||
self.internal
|
self.internal
|
||||||
.ime_mark_text_in_range(&range, text_to_insert.len());
|
.ime_mark_text_in_range(&range, text_to_insert.len());
|
||||||
self.internal.ime_mark_selected_range(
|
self.internal.ime_mark_selected_range(
|
||||||
@@ -78,8 +98,8 @@ impl EntityInputHandler for TextInputState {
|
|||||||
text_to_insert.len(),
|
text_to_insert.len(),
|
||||||
);
|
);
|
||||||
//self.mark_layout_dirty();
|
//self.mark_layout_dirty();
|
||||||
cx.emit(TextChanged);
|
cx.emit_text_changed(TextChanged);
|
||||||
cx.notify();
|
cx.notify_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bounds_for_range(
|
fn bounds_for_range(
|
||||||
@@ -101,3 +121,243 @@ impl EntityInputHandler for TextInputState {
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'app> EditableTextActionHandler<'app> for TextInputState {
|
||||||
|
type Context = gpui::Context<'app, Self>;
|
||||||
|
|
||||||
|
fn escape(&mut self, _: &super::Escape, window: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.set_selected_range(0..0);
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
|
window.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_enter(&mut self, _: &super::Enter, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||||
|
|
||||||
|
fn insert_tab(&mut self, _: &super::Tab, window: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.replace_text_in_range(None, "\t", window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backspace(&mut self, _: &super::Backspace, _: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&mut self, _: &super::Delete, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_word_left(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteWordLeft,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_word_right(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteWordRight,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_to_line_start(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteToBeginningOfLine,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_to_line_end(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteToEndOfLine,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_left(&mut self, _: &super::Left, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_right(&mut self, _: &super::Right, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_up(&mut self, _: &super::Up, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to line
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_down(&mut self, _: &super::Down, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to line
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_line_start(&mut self, _: &super::Home, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to document
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_line_end(&mut self, _: &super::End, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to document
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_start(&mut self, _: &super::MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_end(&mut self, _: &super::MoveToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_left_word(&mut self, _: &super::WordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_right_word(&mut self, _: &super::WordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_all(&mut self, _: &super::SelectAll, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.select_all(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_left(&mut self, _: &super::SelectLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_right(&mut self, _: &super::SelectRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_up(&mut self, _: &super::SelectUp, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to select document
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_down(&mut self, _: &super::SelectDown, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// semantically equivalent to select document
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_start(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectToBeginning,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_end(&mut self, _: &super::SelectToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_left_word(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectWordLeft,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_right_word(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectWordRight,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cut(&mut self, _: &super::Cut, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.cut(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&mut self, _: &super::Copy, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.copy(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, _: &super::Paste, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.paste(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn undo(&mut self, _: &super::Undo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
// TODO: STUB
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redo(&mut self, _: &super::Redo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
// TODO: STUB
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_down(
|
||||||
|
&mut self,
|
||||||
|
event: &gpui::MouseDownEvent,
|
||||||
|
text_position: gpui::Point<gpui::Pixels>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
let character_pos = self.internal.caret_pos(); // TODO: Should be index_for_pixel_point
|
||||||
|
self.internal.on_mouse_down(
|
||||||
|
text_position,
|
||||||
|
character_pos,
|
||||||
|
event.click_count,
|
||||||
|
event.modifiers.shift,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_up(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseUpEvent,
|
||||||
|
_w: &mut Window,
|
||||||
|
_cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal.on_mouse_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_move(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseMoveEvent,
|
||||||
|
text_position: Point<Pixels>,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
let character_pos = self.internal.caret_pos(); // TODO: Should be index_for_pixel_point
|
||||||
|
self.internal.on_mouse_move(character_pos, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ impl TextHistoryPushed {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
new_length: usize,
|
new_length: usize,
|
||||||
storage: impl UnicodeTextStorage,
|
storage: &dyn UnicodeTextStorage,
|
||||||
selected_range: Range<usize>,
|
selected_range: Range<usize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let timestamp = Instant::now();
|
let timestamp = Instant::now();
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
use crate::editable_text::UnicodeTextStorage;
|
use crate::editable_text::{
|
||||||
use gpui::{App, FocusHandle, Focusable, NavigationDirection, Pixels, Point, UTF16Selection};
|
TextBoundary, UnicodeTextStorage,
|
||||||
|
notify::{TextChanged, TextHistoryPushed},
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
App, AppContext, ClipboardItem, FocusHandle, Focusable, NavigationDirection, Pixels, Point,
|
||||||
|
UTF16Selection, Window,
|
||||||
|
};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
pub trait TextStateNotifier {
|
||||||
|
fn notify_changed(&mut self);
|
||||||
|
fn emit_text_changed(&mut self, event: TextChanged);
|
||||||
|
fn emit_history(&mut self, event: TextHistoryPushed);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TextInputStateBase {
|
pub struct TextInputStateBase {
|
||||||
storage: Box<dyn UnicodeTextStorage>,
|
storage: Box<dyn UnicodeTextStorage>,
|
||||||
|
|
||||||
@@ -72,6 +84,10 @@ impl TextInputStateBase {
|
|||||||
pub fn caret_pos(&self) -> usize {
|
pub fn caret_pos(&self) -> usize {
|
||||||
self.selected_range.start
|
self.selected_range.start
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_selected_range(&mut self, range: Range<usize>) {
|
||||||
|
self.selected_range = range;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextInputStateBase {
|
impl TextInputStateBase {
|
||||||
@@ -117,17 +133,36 @@ impl TextInputStateBase {
|
|||||||
range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8)
|
range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_text(&mut self, start: usize, end: usize, new_text: &str) {
|
pub fn replace_text(&mut self, range: &Range<usize>, new_text: &str) {
|
||||||
let storage_len_utf8 = self.storage.content_utf8().len();
|
let storage_len_utf8 = self.storage.content_utf8().len();
|
||||||
let start = start.min(storage_len_utf8);
|
let start = range.start.min(storage_len_utf8);
|
||||||
let end = end.max(start).min(storage_len_utf8);
|
let end = range.end.max(start).min(storage_len_utf8);
|
||||||
self.storage.replace_range(start..end, new_text);
|
self.storage.replace_range(start..end, new_text);
|
||||||
|
|
||||||
let new_caret = start + new_text.len();
|
let new_caret = start + new_text.len();
|
||||||
self.selected_range = new_caret..new_caret;
|
self.selected_range = new_caret..new_caret;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace_text_in_range_bytes(&mut self, range: Range<usize>, mut text_to_insert: &str) {
|
fn emit_change_for_undo(
|
||||||
|
&self,
|
||||||
|
cx: &mut impl TextStateNotifier,
|
||||||
|
range: Range<usize>,
|
||||||
|
length: usize,
|
||||||
|
) {
|
||||||
|
cx.emit_history(TextHistoryPushed::new(
|
||||||
|
range.clone(),
|
||||||
|
length,
|
||||||
|
&*self.storage,
|
||||||
|
self.selected_range.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_text_in_range_bytes(
|
||||||
|
&mut self,
|
||||||
|
range: Range<usize>,
|
||||||
|
mut text_to_insert: &str,
|
||||||
|
cx: &mut impl TextStateNotifier,
|
||||||
|
) {
|
||||||
// TODO: Apply text sanitization
|
// TODO: Apply text sanitization
|
||||||
// single-line fields should prune \n and \r
|
// single-line fields should prune \n and \r
|
||||||
// fields should be able to provide a max_length or other validations on text-input
|
// fields should be able to provide a max_length or other validations on text-input
|
||||||
@@ -142,13 +177,9 @@ impl TextInputStateBase {
|
|||||||
text_to_insert = &text_to_insert[..text_to_insert.len().min(room)];
|
text_to_insert = &text_to_insert[..text_to_insert.len().min(room)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Push history diff
|
self.emit_change_for_undo(cx, range.clone(), text_to_insert.len());
|
||||||
// self.push_undo_patch(range.clone(), text_to_insert.len());
|
|
||||||
|
|
||||||
self.storage.replace_range(range, text_to_insert);
|
self.storage.replace_range(range, text_to_insert);
|
||||||
self.marked_range = None;
|
self.marked_range = None;
|
||||||
|
|
||||||
// TODO: caller emits events
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ime_mark_text_in_range(&mut self, range: &Range<usize>, text_len: usize) {
|
pub fn ime_mark_text_in_range(&mut self, range: &Range<usize>, text_len: usize) {
|
||||||
@@ -178,3 +209,207 @@ impl TextInputStateBase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextInputStateBase {
|
||||||
|
fn move_to(&mut self, caret_pos: usize) {
|
||||||
|
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||||
|
let caret_pos = caret_pos.min(self.storage.content_utf8().len());
|
||||||
|
self.selected_range = caret_pos..caret_pos;
|
||||||
|
//self.scroll_to_cursor();
|
||||||
|
//cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_to(&mut self, caret_pos: usize) {
|
||||||
|
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||||
|
let caret_pos = caret_pos.min(self.storage().content_utf8().len());
|
||||||
|
self.selected_range = caret_pos..self.selected_range.start;
|
||||||
|
//self.scroll_to_cursor();
|
||||||
|
//cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(
|
||||||
|
&mut self,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
boundary: TextBoundary,
|
||||||
|
cx: &mut impl TextStateNotifier,
|
||||||
|
) {
|
||||||
|
let range = self.selected_range();
|
||||||
|
let range = match range.is_empty() {
|
||||||
|
false => range,
|
||||||
|
true => self
|
||||||
|
.storage
|
||||||
|
.range_from_caret(self.caret_pos(), direction, boundary),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.emit_change_for_undo(cx, range.clone(), 0);
|
||||||
|
|
||||||
|
self.replace_text(&range, "");
|
||||||
|
self.marked_range = None;
|
||||||
|
|
||||||
|
cx.emit_text_changed(TextChanged);
|
||||||
|
cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn nav_linear(
|
||||||
|
&mut self,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
boundary: TextBoundary,
|
||||||
|
_cx: &mut impl TextStateNotifier,
|
||||||
|
) {
|
||||||
|
let caret_pos = match self.selected_range.is_empty() {
|
||||||
|
false => match direction {
|
||||||
|
NavigationDirection::Back => self.selected_range.start,
|
||||||
|
NavigationDirection::Forward => self.selected_range.end,
|
||||||
|
},
|
||||||
|
true => self
|
||||||
|
.storage
|
||||||
|
.offset_from_caret(self.caret_pos(), direction, boundary),
|
||||||
|
};
|
||||||
|
self.move_to(caret_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_all(&mut self, _cx: &mut impl TextStateNotifier) {
|
||||||
|
self.selected_range = 0..self.storage.content_utf8().len();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_linear(
|
||||||
|
&mut self,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
boundary: TextBoundary,
|
||||||
|
_cx: &mut impl TextStateNotifier,
|
||||||
|
) {
|
||||||
|
let caret_pos = self
|
||||||
|
.storage
|
||||||
|
.offset_from_caret(self.caret_pos(), direction, boundary);
|
||||||
|
self.select_to(caret_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cut<T>(&mut self, cx: &mut T)
|
||||||
|
where
|
||||||
|
T: TextStateNotifier + std::ops::Deref<Target = App>,
|
||||||
|
{
|
||||||
|
if !self.selected_range.is_empty() {
|
||||||
|
// Cut selected text
|
||||||
|
let slice = &self.storage.content_utf8()[self.selected_range.clone()];
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(slice.to_string()));
|
||||||
|
self.replace_text_in_range_bytes(self.selected_range.clone(), "", cx);
|
||||||
|
} else {
|
||||||
|
// No selection: cut the entire current line (including newline)
|
||||||
|
let caret = self.caret_pos();
|
||||||
|
let line_start = self.storage.find_line_start(caret);
|
||||||
|
let line_end = self.storage.find_line_end(caret);
|
||||||
|
let storage_len_utf8 = self.storage.content_utf8().len();
|
||||||
|
|
||||||
|
// Include the newline character if there is one after the line
|
||||||
|
let cut_end = if line_end < storage_len_utf8 {
|
||||||
|
line_end + 1 // Include the newline
|
||||||
|
} else if line_start > 0 {
|
||||||
|
// Last line with no trailing newline - include preceding newline instead
|
||||||
|
line_end
|
||||||
|
} else {
|
||||||
|
line_end
|
||||||
|
};
|
||||||
|
|
||||||
|
// For last line, also remove the preceding newline if it exists
|
||||||
|
let cut_start = if line_end >= storage_len_utf8 && line_start > 0 {
|
||||||
|
line_start - 1 // Include preceding newline for last line
|
||||||
|
} else {
|
||||||
|
line_start
|
||||||
|
};
|
||||||
|
|
||||||
|
self.selected_range = cut_start..cut_end;
|
||||||
|
|
||||||
|
let slice = &self.storage.content_utf8()[self.selected_range.clone()];
|
||||||
|
cx.write_to_clipboard(ClipboardItem::new_string(slice.to_string()));
|
||||||
|
|
||||||
|
self.replace_text_in_range_bytes(self.selected_range.clone(), "", cx);
|
||||||
|
}
|
||||||
|
cx.emit_text_changed(TextChanged);
|
||||||
|
cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy(&mut self, app: &mut App) {
|
||||||
|
if !self.selected_range.is_empty() {
|
||||||
|
let slice = &self.storage.content_utf8()[self.selected_range.clone()];
|
||||||
|
app.write_to_clipboard(ClipboardItem::new_string(slice.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn paste<T>(&mut self, cx: &mut T)
|
||||||
|
where
|
||||||
|
T: TextStateNotifier + std::ops::Deref<Target = App>,
|
||||||
|
{
|
||||||
|
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.replace_text_in_range_bytes(self.ime_resolve_range(None), &text, cx);
|
||||||
|
cx.emit_text_changed(TextChanged);
|
||||||
|
cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_down<Context>(
|
||||||
|
&mut self,
|
||||||
|
position: Point<Pixels>,
|
||||||
|
character_pos: usize,
|
||||||
|
click_count: usize,
|
||||||
|
shift: bool,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context,
|
||||||
|
) where
|
||||||
|
Context: TextStateNotifier + std::ops::DerefMut<Target = App>,
|
||||||
|
{
|
||||||
|
window.focus(&self.focus_handle, cx);
|
||||||
|
self.is_selecting = true;
|
||||||
|
|
||||||
|
let is_same_position = self
|
||||||
|
.last_click_position
|
||||||
|
.map(|last| {
|
||||||
|
let threshold = gpui::px(4.);
|
||||||
|
(position.x - last.x).abs() < threshold && (position.y - last.y).abs() < threshold
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_same_position && click_count > 1 {
|
||||||
|
self.click_count = click_count;
|
||||||
|
} else {
|
||||||
|
self.click_count = 1;
|
||||||
|
}
|
||||||
|
self.last_click_position = Some(position);
|
||||||
|
|
||||||
|
match self.click_count {
|
||||||
|
2 => {
|
||||||
|
let (word_start, word_end) = self.storage.word_range_at(character_pos);
|
||||||
|
self.selected_range = word_start..word_end;
|
||||||
|
//cx.notify();
|
||||||
|
}
|
||||||
|
3 => {
|
||||||
|
let line_start = self.storage.find_line_start(character_pos);
|
||||||
|
let line_end = self.storage.find_line_end(character_pos);
|
||||||
|
let line_end_with_newline = if line_end < self.storage.content_utf8().len() {
|
||||||
|
line_end + 1
|
||||||
|
} else {
|
||||||
|
line_end
|
||||||
|
};
|
||||||
|
self.selected_range = line_start..line_end_with_newline;
|
||||||
|
//cx.notify();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if shift {
|
||||||
|
self.select_to(character_pos);
|
||||||
|
} else {
|
||||||
|
self.move_to(character_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_up(&mut self) {
|
||||||
|
self.is_selecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_mouse_move(&mut self, character_pos: usize, _cx: &mut impl TextStateNotifier) {
|
||||||
|
if self.is_selecting && self.click_count == 1 {
|
||||||
|
self.select_to(character_pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
|
use gpui::NavigationDirection;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
|
pub enum TextBoundary {
|
||||||
|
/// The next utf-8 character in a direction from the caret
|
||||||
|
Graphmeme,
|
||||||
|
/// The next word in a direction from the caret
|
||||||
|
Word,
|
||||||
|
/// The start/end of the current line
|
||||||
|
Line,
|
||||||
|
/// The rest of the text to the start/end of a document
|
||||||
|
Document,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait UnicodeTextStorage {
|
pub trait UnicodeTextStorage {
|
||||||
/// Returns a reference to the utf8 string.
|
/// Returns a reference to the utf8 string.
|
||||||
fn content_utf8(&self) -> &str;
|
fn content_utf8(&self) -> &str;
|
||||||
@@ -127,6 +139,57 @@ pub trait UnicodeTextStorage {
|
|||||||
len_utf8
|
len_utf8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the utf-8 character position of first character after the first new-line preceeding the character at the provided utf-8 character position.
|
||||||
|
fn find_line_start(&self, position: usize) -> usize {
|
||||||
|
let content = self.content_utf8();
|
||||||
|
content[..position.min(content.len())]
|
||||||
|
.rfind('\n')
|
||||||
|
.map(|pos| pos + 1)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the utf-8 character position of the character immediately before the first new-line character after the character at the provided utf-8 character position.
|
||||||
|
fn find_line_end(&self, position: usize) -> usize {
|
||||||
|
let content = self.content_utf8();
|
||||||
|
content[position.min(content.len())..]
|
||||||
|
.find('\n')
|
||||||
|
.map(|pos| position + pos)
|
||||||
|
.unwrap_or(content.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn range_from_caret(
|
||||||
|
&self,
|
||||||
|
caret: usize,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
magnitude: TextBoundary,
|
||||||
|
) -> Range<usize> {
|
||||||
|
let offset = self.offset_from_caret(caret, direction, magnitude);
|
||||||
|
match direction {
|
||||||
|
NavigationDirection::Back => offset..caret,
|
||||||
|
NavigationDirection::Forward => caret..offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_from_caret(
|
||||||
|
&self,
|
||||||
|
caret: usize,
|
||||||
|
direction: NavigationDirection,
|
||||||
|
magnitude: TextBoundary,
|
||||||
|
) -> usize {
|
||||||
|
use NavigationDirection::*;
|
||||||
|
use TextBoundary::*;
|
||||||
|
match (direction, magnitude) {
|
||||||
|
(Back, Graphmeme) => self.previous_boundary(caret),
|
||||||
|
(Forward, Graphmeme) => self.next_boundary(caret),
|
||||||
|
(Back, Word) => self.previous_word_boundary(caret),
|
||||||
|
(Forward, Word) => self.next_word_boundary(caret),
|
||||||
|
(Back, Line) => self.find_line_start(caret),
|
||||||
|
(Forward, Line) => self.find_line_end(caret),
|
||||||
|
(Back, Document) => 0,
|
||||||
|
(Forward, Document) => self.content_utf8().len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn word_range_at(&self, offset: usize) -> (usize, usize) {
|
fn word_range_at(&self, offset: usize) -> (usize, usize) {
|
||||||
let offset = offset.min(self.content_utf8().len());
|
let offset = offset.min(self.content_utf8().len());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,354 @@
|
|||||||
|
use crate::editable_text::{
|
||||||
|
EditableTextActionHandler, TextBoundary, TextInputStateBase, TextStateNotifier,
|
||||||
|
notify::{TextChanged, TextHistoryPushed},
|
||||||
|
};
|
||||||
|
use gpui::{
|
||||||
|
Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point,
|
||||||
|
UTF16Selection, Window,
|
||||||
|
};
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
|
pub struct TextAreaState {
|
||||||
|
internal: TextInputStateBase,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<TextChanged> for TextAreaState {}
|
||||||
|
impl EventEmitter<TextHistoryPushed> for TextAreaState {}
|
||||||
|
|
||||||
|
impl TextStateNotifier for Context<'_, TextAreaState> {
|
||||||
|
fn notify_changed(&mut self) {
|
||||||
|
self.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_text_changed(&mut self, event: TextChanged) {
|
||||||
|
self.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_history(&mut self, event: TextHistoryPushed) {
|
||||||
|
self.emit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EntityInputHandler for TextAreaState {
|
||||||
|
fn text_for_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
adjusted_range: &mut Option<Range<usize>>,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Option<String> {
|
||||||
|
self.internal
|
||||||
|
.ime_text_for_range(range_utf16, adjusted_range)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(
|
||||||
|
&mut self,
|
||||||
|
ignore_disabled_input: bool,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Option<UTF16Selection> {
|
||||||
|
self.internal.ime_selected_text_range(ignore_disabled_input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marked_text_range(
|
||||||
|
&self,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Option<Range<usize>> {
|
||||||
|
self.internal.ime_marked_text_range()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||||
|
self.internal.ime_unmark_text();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
text_to_insert: &str,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let range_utf8 = self.internal.ime_resolve_range(range_utf16);
|
||||||
|
self.internal
|
||||||
|
.replace_text_in_range_bytes(range_utf8, text_to_insert, cx);
|
||||||
|
//self.mark_layout_dirty();
|
||||||
|
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||||
|
cx.emit_text_changed(TextChanged);
|
||||||
|
cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
text_to_insert: &str,
|
||||||
|
new_selected_range_utf16: Option<Range<usize>>,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
let range = self.internal.ime_resolve_range(range_utf16);
|
||||||
|
self.internal
|
||||||
|
.replace_text_in_range_bytes(range.clone(), text_to_insert, cx);
|
||||||
|
self.internal
|
||||||
|
.ime_mark_text_in_range(&range, text_to_insert.len());
|
||||||
|
self.internal.ime_mark_selected_range(
|
||||||
|
&range,
|
||||||
|
&new_selected_range_utf16,
|
||||||
|
text_to_insert.len(),
|
||||||
|
);
|
||||||
|
//self.mark_layout_dirty();
|
||||||
|
cx.emit_text_changed(TextChanged);
|
||||||
|
cx.notify_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds_for_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
bounds: Bounds<Pixels>,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Option<Bounds<Pixels>> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn character_index_for_point(
|
||||||
|
&mut self,
|
||||||
|
point: Point<Pixels>,
|
||||||
|
_window: &mut Window,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'app> EditableTextActionHandler<'app> for TextAreaState {
|
||||||
|
type Context = gpui::Context<'app, Self>;
|
||||||
|
|
||||||
|
fn escape(&mut self, _: &super::Escape, window: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.set_selected_range(0..0);
|
||||||
|
cx.notify();
|
||||||
|
|
||||||
|
window.blur();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_enter(&mut self, _: &super::Enter, window: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.replace_text_in_range(None, "\n", window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_tab(&mut self, _: &super::Tab, window: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.replace_text_in_range(None, "\t", window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backspace(&mut self, _: &super::Backspace, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(&mut self, _: &super::Delete, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_word_left(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteWordLeft,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_word_right(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteWordRight,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_to_line_start(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteToBeginningOfLine,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_to_line_end(
|
||||||
|
&mut self,
|
||||||
|
_: &super::DeleteToEndOfLine,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.delete(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_left(&mut self, _: &super::Left, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_right(&mut self, _: &super::Right, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_up(&mut self, _: &super::Up, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_down(&mut self, _: &super::Down, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_line_start(&mut self, _: &super::Home, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_line_end(&mut self, _: &super::End, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_start(&mut self, _: &super::MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_end(&mut self, _: &super::MoveToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_left_word(&mut self, _: &super::WordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nav_right_word(&mut self, _: &super::WordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_all(&mut self, _: &super::SelectAll, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.select_all(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_left(&mut self, _: &super::SelectLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_right(&mut self, _: &super::SelectRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_up(&mut self, _: &super::SelectUp, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_down(&mut self, _: &super::SelectDown, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
// TODO: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_start(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectToBeginning,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_end(&mut self, _: &super::SelectToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_left_word(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectWordLeft,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_right_word(
|
||||||
|
&mut self,
|
||||||
|
_: &super::SelectWordRight,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal
|
||||||
|
.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cut(&mut self, _: &super::Cut, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.cut(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy(&mut self, _: &super::Copy, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.copy(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paste(&mut self, _: &super::Paste, _w: &mut Window, cx: &mut Self::Context) {
|
||||||
|
self.internal.paste(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn undo(&mut self, _: &super::Undo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
// TODO: STUB
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redo(&mut self, _: &super::Redo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||||
|
// TODO: STUB
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_down(
|
||||||
|
&mut self,
|
||||||
|
event: &gpui::MouseDownEvent,
|
||||||
|
text_position: gpui::Point<gpui::Pixels>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
let character_pos = self.internal.caret_pos(); // TODO: Should be index_for_pixel_point
|
||||||
|
self.internal.on_mouse_down(
|
||||||
|
text_position,
|
||||||
|
character_pos,
|
||||||
|
event.click_count,
|
||||||
|
event.modifiers.shift,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_up(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseUpEvent,
|
||||||
|
_w: &mut Window,
|
||||||
|
_cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
self.internal.on_mouse_up();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_mouse_move(
|
||||||
|
&mut self,
|
||||||
|
_event: &gpui::MouseMoveEvent,
|
||||||
|
text_position: Point<Pixels>,
|
||||||
|
_w: &mut Window,
|
||||||
|
cx: &mut Self::Context,
|
||||||
|
) {
|
||||||
|
let character_pos = self.internal.caret_pos(); // TODO: Should be index_for_pixel_point
|
||||||
|
self.internal.on_mouse_move(character_pos, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use super::actions::*;
|
use super::actions::*;
|
||||||
use crate::input::{InputLayoutStyle, InputStorage};
|
use crate::input::{InputLayoutStyle, InputStorage};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, ClipboardItem, Context, Entity, EntityId, EntityInputHandler, EventEmitter,
|
App, ClipboardItem, Context, EntityId, EntityInputHandler, EventEmitter, FocusHandle,
|
||||||
FocusHandle, Focusable, NavigationDirection, Pixels, Point, Render, SharedString, Size,
|
Focusable, NavigationDirection, Pixels, Point, SharedString, Size, TextRun, TextStyle, Window,
|
||||||
Subscription, TextRun, TextStyle, Window, WrappedLine, point, px,
|
WrappedLine, point, px,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
|||||||
Reference in New Issue
Block a user