T06a forms text: text input core, fields, numbers, masks, pin, inline, groups, tags, pairs, path, regex, expression, hotkey, mention
This commit is contained in:
41 files changed
+6233
-45
No files matched your search
@@ -0,0 +1,391 @@
|
||||
use gpui::{
|
||||
App, ClipboardItem, Context, CursorStyle, InteractiveElement, IntoElement, KeyBinding,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, Window, actions,
|
||||
div,
|
||||
};
|
||||
|
||||
use super::{
|
||||
InputEvent, TextInput,
|
||||
edit::{self, next_grapheme, prev_grapheme},
|
||||
element::TextElement,
|
||||
};
|
||||
|
||||
actions!(
|
||||
ely_input,
|
||||
[
|
||||
Backspace,
|
||||
Delete,
|
||||
DeleteWordLeft,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
SelectLeft,
|
||||
SelectRight,
|
||||
SelectUp,
|
||||
SelectDown,
|
||||
WordLeft,
|
||||
WordRight,
|
||||
SelectWordLeft,
|
||||
SelectWordRight,
|
||||
Home,
|
||||
End,
|
||||
SelectHome,
|
||||
SelectEnd,
|
||||
SelectAll,
|
||||
Copy,
|
||||
Cut,
|
||||
Paste,
|
||||
Undo,
|
||||
Redo,
|
||||
Enter,
|
||||
Submit,
|
||||
ShowCharacterPalette,
|
||||
]
|
||||
);
|
||||
|
||||
const CONTEXT: &str = "ElyInput";
|
||||
|
||||
/// Binds the text keys. `init` calls it.
|
||||
pub(crate) fn bind_keys(cx: &mut App) {
|
||||
let word = if cfg!(target_os = "macos") {
|
||||
"alt"
|
||||
} else {
|
||||
"ctrl"
|
||||
};
|
||||
let mut bindings = vec![
|
||||
KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
|
||||
KeyBinding::new("delete", Delete, Some(CONTEXT)),
|
||||
KeyBinding::new(&format!("{word}-backspace"), DeleteWordLeft, Some(CONTEXT)),
|
||||
KeyBinding::new("left", Left, Some(CONTEXT)),
|
||||
KeyBinding::new("right", Right, Some(CONTEXT)),
|
||||
KeyBinding::new("up", Up, Some(CONTEXT)),
|
||||
KeyBinding::new("down", Down, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-left", SelectLeft, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-right", SelectRight, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-up", SelectUp, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-down", SelectDown, Some(CONTEXT)),
|
||||
KeyBinding::new(&format!("{word}-left"), WordLeft, Some(CONTEXT)),
|
||||
KeyBinding::new(&format!("{word}-right"), WordRight, Some(CONTEXT)),
|
||||
KeyBinding::new(&format!("{word}-shift-left"), SelectWordLeft, Some(CONTEXT)),
|
||||
KeyBinding::new(
|
||||
&format!("{word}-shift-right"),
|
||||
SelectWordRight,
|
||||
Some(CONTEXT),
|
||||
),
|
||||
KeyBinding::new("home", Home, Some(CONTEXT)),
|
||||
KeyBinding::new("end", End, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-home", SelectHome, Some(CONTEXT)),
|
||||
KeyBinding::new("shift-end", SelectEnd, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-a", SelectAll, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-c", Copy, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-x", Cut, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-v", Paste, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-z", Undo, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-shift-z", Redo, Some(CONTEXT)),
|
||||
KeyBinding::new("enter", Enter, Some(CONTEXT)),
|
||||
KeyBinding::new("secondary-enter", Submit, Some(CONTEXT)),
|
||||
];
|
||||
if cfg!(target_os = "macos") {
|
||||
bindings.extend([
|
||||
KeyBinding::new("cmd-left", Home, Some(CONTEXT)),
|
||||
KeyBinding::new("cmd-right", End, Some(CONTEXT)),
|
||||
KeyBinding::new("cmd-shift-left", SelectHome, Some(CONTEXT)),
|
||||
KeyBinding::new("cmd-shift-right", SelectEnd, Some(CONTEXT)),
|
||||
KeyBinding::new("ctrl-cmd-space", ShowCharacterPalette, Some(CONTEXT)),
|
||||
]);
|
||||
} else {
|
||||
bindings.push(KeyBinding::new("ctrl-y", Redo, Some(CONTEXT)));
|
||||
}
|
||||
cx.bind_keys(bindings);
|
||||
}
|
||||
|
||||
impl TextInput {
|
||||
fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let range = if self.selection.is_empty() {
|
||||
prev_grapheme(&self.text, self.cursor())..self.cursor()
|
||||
} else {
|
||||
self.selection.clone()
|
||||
};
|
||||
self.replace(range, "", false, cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let range = if self.selection.is_empty() {
|
||||
self.cursor()..next_grapheme(&self.text, self.cursor())
|
||||
} else {
|
||||
self.selection.clone()
|
||||
};
|
||||
self.replace(range, "", false, cx);
|
||||
}
|
||||
|
||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let range = if self.selection.is_empty() {
|
||||
edit::prev_word(&self.text, self.cursor())..self.cursor()
|
||||
} else {
|
||||
self.selection.clone()
|
||||
};
|
||||
self.replace(range, "", false, cx);
|
||||
}
|
||||
|
||||
fn left(&mut self, _: &Left, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = if self.selection.is_empty() {
|
||||
prev_grapheme(&self.text, self.cursor())
|
||||
} else {
|
||||
self.selection.start
|
||||
};
|
||||
self.move_to(to, cx);
|
||||
}
|
||||
|
||||
fn right(&mut self, _: &Right, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = if self.selection.is_empty() {
|
||||
next_grapheme(&self.text, self.cursor())
|
||||
} else {
|
||||
self.selection.end
|
||||
};
|
||||
self.move_to(to, cx);
|
||||
}
|
||||
|
||||
/// The offset one visual line above or below the caret.
|
||||
fn vertical(&self, down: bool) -> usize {
|
||||
let caret = self.cursor();
|
||||
let (Some(layout), Some(at)) = (
|
||||
self.layout.as_ref(),
|
||||
self.position_for(self.display_offset(caret)),
|
||||
) else {
|
||||
return caret;
|
||||
};
|
||||
let step = if down {
|
||||
layout.line_height
|
||||
} else {
|
||||
-layout.line_height
|
||||
};
|
||||
let target = gpui::point(at.x, at.y + step + layout.line_height / 2.0);
|
||||
if target.y < gpui::Pixels::ZERO {
|
||||
return 0;
|
||||
}
|
||||
self.offset_for_point(layout.bounds.origin + target - self.scroll)
|
||||
}
|
||||
|
||||
fn up(&mut self, _: &Up, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = self.vertical(false);
|
||||
self.move_to(to, cx);
|
||||
}
|
||||
|
||||
fn down(&mut self, _: &Down, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = if self.rows.is_some() {
|
||||
self.vertical(true)
|
||||
} else {
|
||||
self.text.len()
|
||||
};
|
||||
self.move_to(to, cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(prev_grapheme(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(next_grapheme(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = self.vertical(false);
|
||||
self.select_to(to, cx);
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let to = self.vertical(true);
|
||||
self.select_to(to, cx);
|
||||
}
|
||||
|
||||
fn word_left(&mut self, _: &WordLeft, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(edit::prev_word(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn word_right(&mut self, _: &WordRight, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(edit::next_word(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_word_left(&mut self, _: &SelectWordLeft, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(edit::prev_word(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_word_right(&mut self, _: &SelectWordRight, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(edit::next_word(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn home(&mut self, _: &Home, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(edit::line_start(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn end(&mut self, _: &End, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(edit::line_end(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_home(&mut self, _: &SelectHome, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(edit::line_start(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_end(&mut self, _: &SelectEnd, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(edit::line_end(&self.text, self.cursor()), cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selection = 0..self.text.len();
|
||||
self.reversed = false;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.masked || self.selection.is_empty() {
|
||||
return;
|
||||
}
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
self.text[self.selection.clone()].to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.masked || self.selection.is_empty() {
|
||||
return;
|
||||
}
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(
|
||||
self.text[self.selection.clone()].to_string(),
|
||||
));
|
||||
self.replace(self.selection.clone(), "", false, cx);
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
|
||||
return;
|
||||
};
|
||||
self.replace(self.selection.clone(), &text, false, cx);
|
||||
}
|
||||
|
||||
fn undo(&mut self, _: &Undo, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.disabled {
|
||||
return;
|
||||
}
|
||||
if let Some(previous) = self.history.undo(self.snapshot()) {
|
||||
self.restore(previous, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.disabled {
|
||||
return;
|
||||
}
|
||||
if let Some(next) = self.history.redo(self.snapshot()) {
|
||||
self.restore(next, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn enter(&mut self, _: &Enter, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.rows.is_some() {
|
||||
self.replace(self.selection.clone(), "\n", false, cx);
|
||||
} else {
|
||||
log::info!("text input: submitted");
|
||||
cx.emit(InputEvent::Submit);
|
||||
}
|
||||
}
|
||||
|
||||
fn submit(&mut self, _: &Submit, _: &mut Window, cx: &mut Context<Self>) {
|
||||
log::info!("text input: submitted");
|
||||
cx.emit(InputEvent::Submit);
|
||||
}
|
||||
|
||||
fn show_character_palette(
|
||||
&mut self,
|
||||
_: &ShowCharacterPalette,
|
||||
window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) {
|
||||
window.show_character_palette();
|
||||
}
|
||||
|
||||
fn mouse_down(&mut self, event: &MouseDownEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.disabled {
|
||||
return;
|
||||
}
|
||||
window.focus(&self.focus);
|
||||
let at = self.offset_for_point(event.position);
|
||||
match event.click_count {
|
||||
2 => {
|
||||
let word = edit::word_at(&self.text, at);
|
||||
self.selection = word;
|
||||
self.reversed = false;
|
||||
cx.notify();
|
||||
}
|
||||
3.. => {
|
||||
self.selection = edit::line_start(&self.text, at)..edit::line_end(&self.text, at);
|
||||
self.reversed = false;
|
||||
cx.notify();
|
||||
}
|
||||
_ if event.modifiers.shift => self.select_to(at, cx),
|
||||
_ => {
|
||||
self.selecting = true;
|
||||
self.move_to(at, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_move(&mut self, event: &MouseMoveEvent, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selecting && event.dragging() {
|
||||
let at = self.offset_for_point(event.position);
|
||||
self.select_to(at, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn mouse_up(&mut self, _: &gpui::MouseUpEvent, _: &mut Window, _: &mut Context<Self>) {
|
||||
self.selecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextInput {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.id("ely-text-input")
|
||||
.key_context(CONTEXT)
|
||||
.track_focus(&self.focus)
|
||||
.w_full()
|
||||
.cursor(if self.disabled {
|
||||
CursorStyle::OperationNotAllowed
|
||||
} else {
|
||||
CursorStyle::IBeam
|
||||
})
|
||||
.on_action(cx.listener(Self::backspace))
|
||||
.on_action(cx.listener(Self::delete))
|
||||
.on_action(cx.listener(Self::delete_word_left))
|
||||
.on_action(cx.listener(Self::left))
|
||||
.on_action(cx.listener(Self::right))
|
||||
.on_action(cx.listener(Self::up))
|
||||
.on_action(cx.listener(Self::down))
|
||||
.on_action(cx.listener(Self::select_left))
|
||||
.on_action(cx.listener(Self::select_right))
|
||||
.on_action(cx.listener(Self::select_up))
|
||||
.on_action(cx.listener(Self::select_down))
|
||||
.on_action(cx.listener(Self::word_left))
|
||||
.on_action(cx.listener(Self::word_right))
|
||||
.on_action(cx.listener(Self::select_word_left))
|
||||
.on_action(cx.listener(Self::select_word_right))
|
||||
.on_action(cx.listener(Self::home))
|
||||
.on_action(cx.listener(Self::end))
|
||||
.on_action(cx.listener(Self::select_home))
|
||||
.on_action(cx.listener(Self::select_end))
|
||||
.on_action(cx.listener(Self::select_all))
|
||||
.on_action(cx.listener(Self::copy))
|
||||
.on_action(cx.listener(Self::cut))
|
||||
.on_action(cx.listener(Self::paste))
|
||||
.on_action(cx.listener(Self::undo))
|
||||
.on_action(cx.listener(Self::redo))
|
||||
.on_action(cx.listener(Self::enter))
|
||||
.on_action(cx.listener(Self::submit))
|
||||
.on_action(cx.listener(Self::show_character_palette))
|
||||
.on_mouse_down(MouseButton::Left, cx.listener(Self::mouse_down))
|
||||
.on_mouse_move(cx.listener(Self::mouse_move))
|
||||
.on_mouse_up(MouseButton::Left, cx.listener(Self::mouse_up))
|
||||
.on_mouse_up_out(MouseButton::Left, cx.listener(Self::mouse_up))
|
||||
.child(TextElement::new(cx.entity()))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user