Shared: generic undo History, OverflowMenu icon and tooltip, StatusBarItem leading, six Lucide icons.
288 lines
9.8 KiB
Rust
288 lines
9.8 KiB
Rust
use gpui::{App, Context, InteractiveElement, KeyBinding, Window, actions};
|
|
|
|
use super::{buffer::Buffer, cursor::Motion, state::CodeEditor};
|
|
|
|
actions!(
|
|
ely_editor,
|
|
[
|
|
Left,
|
|
Right,
|
|
Up,
|
|
Down,
|
|
SelectLeft,
|
|
SelectRight,
|
|
SelectUp,
|
|
SelectDown,
|
|
WordLeft,
|
|
WordRight,
|
|
SelectWordLeft,
|
|
SelectWordRight,
|
|
LineStart,
|
|
LineEnd,
|
|
SelectLineStart,
|
|
SelectLineEnd,
|
|
DocumentStart,
|
|
DocumentEnd,
|
|
SelectDocumentStart,
|
|
SelectDocumentEnd,
|
|
PageUp,
|
|
PageDown,
|
|
Backspace,
|
|
Delete,
|
|
DeleteWordLeft,
|
|
DeleteLineLeft,
|
|
Newline,
|
|
Indent,
|
|
Outdent,
|
|
Undo,
|
|
Redo,
|
|
SelectAll,
|
|
Copy,
|
|
Cut,
|
|
Paste,
|
|
AddCursorAbove,
|
|
AddCursorBelow,
|
|
SelectNext,
|
|
SingleCursor,
|
|
ToggleComment,
|
|
Fold,
|
|
Unfold,
|
|
]
|
|
);
|
|
|
|
pub(crate) const CONTEXT: &str = "ElyEditor";
|
|
|
|
/// Binds the editor's keys. `init` calls it.
|
|
pub(crate) fn bind_keys(cx: &mut App) {
|
|
let word = if cfg!(target_os = "macos") {
|
|
"alt"
|
|
} else {
|
|
"ctrl"
|
|
};
|
|
let context = Some(CONTEXT);
|
|
let mut bindings = vec![
|
|
KeyBinding::new("left", Left, context),
|
|
KeyBinding::new("right", Right, context),
|
|
KeyBinding::new("up", Up, context),
|
|
KeyBinding::new("down", Down, context),
|
|
KeyBinding::new("shift-left", SelectLeft, context),
|
|
KeyBinding::new("shift-right", SelectRight, context),
|
|
KeyBinding::new("shift-up", SelectUp, context),
|
|
KeyBinding::new("shift-down", SelectDown, context),
|
|
KeyBinding::new(&format!("{word}-left"), WordLeft, context),
|
|
KeyBinding::new(&format!("{word}-right"), WordRight, context),
|
|
KeyBinding::new(&format!("{word}-shift-left"), SelectWordLeft, context),
|
|
KeyBinding::new(&format!("{word}-shift-right"), SelectWordRight, context),
|
|
KeyBinding::new("home", LineStart, context),
|
|
KeyBinding::new("end", LineEnd, context),
|
|
KeyBinding::new("shift-home", SelectLineStart, context),
|
|
KeyBinding::new("shift-end", SelectLineEnd, context),
|
|
KeyBinding::new("pageup", PageUp, context),
|
|
KeyBinding::new("pagedown", PageDown, context),
|
|
KeyBinding::new("backspace", Backspace, context),
|
|
KeyBinding::new("shift-backspace", Backspace, context),
|
|
KeyBinding::new("delete", Delete, context),
|
|
KeyBinding::new(&format!("{word}-backspace"), DeleteWordLeft, context),
|
|
KeyBinding::new("enter", Newline, context),
|
|
KeyBinding::new("tab", Indent, context),
|
|
KeyBinding::new("shift-tab", Outdent, context),
|
|
KeyBinding::new("secondary-z", Undo, context),
|
|
KeyBinding::new("secondary-shift-z", Redo, context),
|
|
KeyBinding::new("secondary-a", SelectAll, context),
|
|
KeyBinding::new("secondary-c", Copy, context),
|
|
KeyBinding::new("secondary-x", Cut, context),
|
|
KeyBinding::new("secondary-v", Paste, context),
|
|
KeyBinding::new("secondary-d", SelectNext, context),
|
|
KeyBinding::new("escape", SingleCursor, context),
|
|
KeyBinding::new("secondary-/", ToggleComment, context),
|
|
KeyBinding::new("secondary-alt-[", Fold, context),
|
|
KeyBinding::new("secondary-alt-]", Unfold, context),
|
|
];
|
|
if cfg!(target_os = "macos") {
|
|
bindings.extend([
|
|
KeyBinding::new("cmd-left", LineStart, context),
|
|
KeyBinding::new("cmd-right", LineEnd, context),
|
|
KeyBinding::new("cmd-shift-left", SelectLineStart, context),
|
|
KeyBinding::new("cmd-shift-right", SelectLineEnd, context),
|
|
KeyBinding::new("cmd-up", DocumentStart, context),
|
|
KeyBinding::new("cmd-down", DocumentEnd, context),
|
|
KeyBinding::new("cmd-shift-up", SelectDocumentStart, context),
|
|
KeyBinding::new("cmd-shift-down", SelectDocumentEnd, context),
|
|
KeyBinding::new("cmd-backspace", DeleteLineLeft, context),
|
|
KeyBinding::new("cmd-alt-up", AddCursorAbove, context),
|
|
KeyBinding::new("cmd-alt-down", AddCursorBelow, context),
|
|
]);
|
|
} else {
|
|
bindings.extend([
|
|
KeyBinding::new("ctrl-home", DocumentStart, context),
|
|
KeyBinding::new("ctrl-end", DocumentEnd, context),
|
|
KeyBinding::new("ctrl-shift-home", SelectDocumentStart, context),
|
|
KeyBinding::new("ctrl-shift-end", SelectDocumentEnd, context),
|
|
KeyBinding::new("ctrl-alt-up", AddCursorAbove, context),
|
|
KeyBinding::new("ctrl-alt-down", AddCursorBelow, context),
|
|
KeyBinding::new("ctrl-y", Redo, context),
|
|
]);
|
|
}
|
|
cx.bind_keys(bindings);
|
|
}
|
|
|
|
/// Rows a page key moves.
|
|
const PAGE: isize = 20;
|
|
|
|
fn line_left(buffer: &Buffer, at: usize) -> std::ops::Range<usize> {
|
|
buffer.line_range(buffer.line_of(at)).start..at
|
|
}
|
|
|
|
/// Wires every action to the editor that renders `root`.
|
|
pub(crate) fn listen<E: InteractiveElement>(root: E, cx: &mut Context<CodeEditor>) -> E {
|
|
macro_rules! on {
|
|
($root:expr, $action:ty, $body:expr) => {
|
|
$root.on_action(cx.listener(
|
|
move |editor: &mut CodeEditor,
|
|
_: &$action,
|
|
window: &mut Window,
|
|
cx: &mut Context<CodeEditor>| {
|
|
let run: fn(&mut CodeEditor, &mut Window, &mut Context<CodeEditor>) = $body;
|
|
run(editor, window, cx)
|
|
},
|
|
))
|
|
};
|
|
}
|
|
let root = on!(root, Left, |e, _, cx| e.motion(Motion::Left, false, cx));
|
|
let root = on!(root, Right, |e, _, cx| e.motion(Motion::Right, false, cx));
|
|
let root = on!(root, Up, |e, _, cx| e.motion(Motion::Up, false, cx));
|
|
let root = on!(root, Down, |e, _, cx| e.motion(Motion::Down, false, cx));
|
|
let root = on!(root, SelectLeft, |e, _, cx| e.motion(
|
|
Motion::Left,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectRight, |e, _, cx| e.motion(
|
|
Motion::Right,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectUp, |e, _, cx| e.motion(Motion::Up, true, cx));
|
|
let root = on!(root, SelectDown, |e, _, cx| e.motion(
|
|
Motion::Down,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, WordLeft, |e, _, cx| e.motion(
|
|
Motion::WordLeft,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, WordRight, |e, _, cx| e.motion(
|
|
Motion::WordRight,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectWordLeft, |e, _, cx| e.motion(
|
|
Motion::WordLeft,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectWordRight, |e, _, cx| e.motion(
|
|
Motion::WordRight,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, LineStart, |e, _, cx| e.motion(
|
|
Motion::LineStart,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, LineEnd, |e, _, cx| e.motion(
|
|
Motion::LineEnd,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectLineStart, |e, _, cx| e.motion(
|
|
Motion::LineStart,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectLineEnd, |e, _, cx| e.motion(
|
|
Motion::LineEnd,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, DocumentStart, |e, _, cx| e.motion(
|
|
Motion::Start,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, DocumentEnd, |e, _, cx| e.motion(
|
|
Motion::End,
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectDocumentStart, |e, _, cx| e.motion(
|
|
Motion::Start,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, SelectDocumentEnd, |e, _, cx| e.motion(
|
|
Motion::End,
|
|
true,
|
|
cx
|
|
));
|
|
let root = on!(root, PageUp, |e, _, cx| e.motion(
|
|
Motion::Page(-PAGE),
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, PageDown, |e, _, cx| e.motion(
|
|
Motion::Page(PAGE),
|
|
false,
|
|
cx
|
|
));
|
|
let root = on!(root, Backspace, |e, _, cx| {
|
|
e.delete_by(|buffer, at| buffer.previous(at)..at, cx)
|
|
});
|
|
let root = on!(root, Delete, |e, _, cx| e
|
|
.delete_by(|buffer, at| at..buffer.next(at), cx));
|
|
let root = on!(root, DeleteWordLeft, |e, _, cx| {
|
|
e.delete_by(|buffer, at| buffer.word_start(at)..at, cx)
|
|
});
|
|
let root = on!(root, DeleteLineLeft, |e, _, cx| e.delete_by(line_left, cx));
|
|
let root = on!(root, Newline, |e, _, cx| e.newline(cx));
|
|
let root = on!(root, Indent, |e, _, cx| e.indent(cx));
|
|
let root = on!(root, Outdent, |e, _, cx| e.outdent(cx));
|
|
let root = on!(root, Undo, |e, _, cx| e.undo(cx));
|
|
let root = on!(root, Redo, |e, _, cx| e.redo(cx));
|
|
let root = on!(root, SelectAll, |e, _, cx| e.select_all(cx));
|
|
let root = on!(root, Copy, |e, _, cx| e.copy(cx));
|
|
let root = on!(root, Cut, |e, _, cx| e.cut(cx));
|
|
let root = on!(root, Paste, |e, _, cx| e.paste(cx));
|
|
let root = on!(root, AddCursorAbove, |e, _, cx| e.add_cursor(false, cx));
|
|
let root = on!(root, AddCursorBelow, |e, _, cx| e.add_cursor(true, cx));
|
|
let root = on!(root, SelectNext, |e, _, cx| e.select_next(cx));
|
|
let root = on!(root, SingleCursor, |e, window, cx| {
|
|
if e.marks.chat.is_some() {
|
|
e.set_chat(None, window, cx)
|
|
} else {
|
|
e.single_cursor(cx)
|
|
}
|
|
});
|
|
let root = on!(root, ToggleComment, |e, _, cx| e.toggle_comment(cx));
|
|
let root = on!(root, Fold, |e, _, cx| {
|
|
let line = e.buffer.line_of(e.primary().head);
|
|
let header = super::syntax::folds(&e.buffer)
|
|
.into_iter()
|
|
.filter(|(header, end)| *header <= line && line <= *end)
|
|
.map(|(header, _)| header)
|
|
.next_back();
|
|
if let Some(header) = header.filter(|header| !e.folded.contains(header)) {
|
|
e.toggle_fold(header, cx);
|
|
}
|
|
});
|
|
on!(root, Unfold, |e, _, cx| {
|
|
let line = e.buffer.line_of(e.primary().head);
|
|
if e.folded.contains(&line) {
|
|
e.toggle_fold(line, cx);
|
|
}
|
|
})
|
|
}
|