|
|
@ -13,7 +13,6 @@ use helix_core::{
|
|
|
|
use helix_view::{
|
|
|
|
use helix_view::{
|
|
|
|
document::{IndentStyle, Mode},
|
|
|
|
document::{IndentStyle, Mode},
|
|
|
|
editor::Action,
|
|
|
|
editor::Action,
|
|
|
|
info::Info,
|
|
|
|
|
|
|
|
input::KeyEvent,
|
|
|
|
input::KeyEvent,
|
|
|
|
keyboard::KeyCode,
|
|
|
|
keyboard::KeyCode,
|
|
|
|
view::{View, PADDING},
|
|
|
|
view::{View, PADDING},
|
|
|
@ -36,7 +35,6 @@ use crate::{
|
|
|
|
|
|
|
|
|
|
|
|
use crate::job::{self, Job, Jobs};
|
|
|
|
use crate::job::{self, Job, Jobs};
|
|
|
|
use futures_util::{FutureExt, TryFutureExt};
|
|
|
|
use futures_util::{FutureExt, TryFutureExt};
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
use std::num::NonZeroUsize;
|
|
|
|
use std::num::NonZeroUsize;
|
|
|
|
use std::{fmt, future::Future};
|
|
|
|
use std::{fmt, future::Future};
|
|
|
|
|
|
|
|
|
|
|
@ -45,7 +43,7 @@ use std::{
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
use once_cell::sync::{Lazy, OnceCell};
|
|
|
|
use once_cell::sync::Lazy;
|
|
|
|
use serde::de::{self, Deserialize, Deserializer};
|
|
|
|
use serde::de::{self, Deserialize, Deserializer};
|
|
|
|
|
|
|
|
|
|
|
|
pub struct Context<'a> {
|
|
|
|
pub struct Context<'a> {
|
|
|
@ -74,18 +72,6 @@ impl<'a> Context<'a> {
|
|
|
|
self.on_next_key_callback = Some(Box::new(on_next_key_callback));
|
|
|
|
self.on_next_key_callback = Some(Box::new(on_next_key_callback));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
pub fn on_next_key_mode(&mut self, map: HashMap<KeyEvent, fn(&mut Context)>) {
|
|
|
|
|
|
|
|
let count = self.count;
|
|
|
|
|
|
|
|
self.on_next_key(move |cx, event| {
|
|
|
|
|
|
|
|
cx.count = count;
|
|
|
|
|
|
|
|
cx.editor.autoinfo = None;
|
|
|
|
|
|
|
|
if let Some(func) = map.get(&event) {
|
|
|
|
|
|
|
|
func(cx);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
pub fn callback<T, F>(
|
|
|
|
pub fn callback<T, F>(
|
|
|
|
&mut self,
|
|
|
|
&mut self,
|
|
|
@ -139,13 +125,21 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
|
|
|
|
/// A command is composed of a static name, and a function that takes the current state plus a count,
|
|
|
|
/// A command is composed of a static name, and a function that takes the current state plus a count,
|
|
|
|
/// and does a side-effect on the state (usually by creating and applying a transaction).
|
|
|
|
/// and does a side-effect on the state (usually by creating and applying a transaction).
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
#[derive(Copy, Clone)]
|
|
|
|
pub struct Command(&'static str, fn(cx: &mut Context));
|
|
|
|
pub struct Command {
|
|
|
|
|
|
|
|
name: &'static str,
|
|
|
|
|
|
|
|
fun: fn(cx: &mut Context),
|
|
|
|
|
|
|
|
doc: &'static str,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
macro_rules! commands {
|
|
|
|
macro_rules! commands {
|
|
|
|
( $($name:ident),* ) => {
|
|
|
|
( $($name:ident, $doc:literal),* ) => {
|
|
|
|
$(
|
|
|
|
$(
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
|
|
pub const $name: Self = Self(stringify!($name), $name);
|
|
|
|
pub const $name: Self = Self {
|
|
|
|
|
|
|
|
name: stringify!($name),
|
|
|
|
|
|
|
|
fun: $name,
|
|
|
|
|
|
|
|
doc: $doc
|
|
|
|
|
|
|
|
};
|
|
|
|
)*
|
|
|
|
)*
|
|
|
|
|
|
|
|
|
|
|
|
pub const COMMAND_LIST: &'static [Self] = &[
|
|
|
|
pub const COMMAND_LIST: &'static [Self] = &[
|
|
|
@ -156,144 +150,159 @@ macro_rules! commands {
|
|
|
|
|
|
|
|
|
|
|
|
impl Command {
|
|
|
|
impl Command {
|
|
|
|
pub fn execute(&self, cx: &mut Context) {
|
|
|
|
pub fn execute(&self, cx: &mut Context) {
|
|
|
|
(self.1)(cx);
|
|
|
|
(self.fun)(cx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
pub fn name(&self) -> &'static str {
|
|
|
|
self.0
|
|
|
|
self.name
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn doc(&self) -> &'static str {
|
|
|
|
|
|
|
|
self.doc
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[rustfmt::skip]
|
|
|
|
commands!(
|
|
|
|
commands!(
|
|
|
|
move_char_left,
|
|
|
|
move_char_left, "Move left",
|
|
|
|
move_char_right,
|
|
|
|
move_char_right, "Move right",
|
|
|
|
move_line_up,
|
|
|
|
move_line_up, "Move up",
|
|
|
|
move_line_down,
|
|
|
|
move_line_down, "Move down",
|
|
|
|
move_next_word_start,
|
|
|
|
extend_char_left, "Extend left",
|
|
|
|
move_prev_word_start,
|
|
|
|
extend_char_right, "Extend right",
|
|
|
|
move_next_word_end,
|
|
|
|
extend_line_up, "Extend up",
|
|
|
|
move_next_long_word_start,
|
|
|
|
extend_line_down, "Extend down",
|
|
|
|
move_prev_long_word_start,
|
|
|
|
move_next_word_start, "Move to beginning of next word",
|
|
|
|
move_next_long_word_end,
|
|
|
|
move_prev_word_start, "Move to beginning of previous word",
|
|
|
|
extend_next_word_start,
|
|
|
|
move_next_word_end, "Move to end of next word",
|
|
|
|
extend_prev_word_start,
|
|
|
|
move_next_long_word_start, "Move to beginning of next long word",
|
|
|
|
extend_next_word_end,
|
|
|
|
move_prev_long_word_start, "Move to beginning of previous long word",
|
|
|
|
find_till_char,
|
|
|
|
move_next_long_word_end, "Move to end of next long word",
|
|
|
|
find_next_char,
|
|
|
|
extend_next_word_start, "Extend to beginning of next word",
|
|
|
|
extend_till_char,
|
|
|
|
extend_prev_word_start, "Extend to beginning of previous word",
|
|
|
|
extend_next_char,
|
|
|
|
extend_next_word_end, "Extend to end of next word",
|
|
|
|
till_prev_char,
|
|
|
|
find_till_char, "Move till next occurance of char",
|
|
|
|
find_prev_char,
|
|
|
|
find_next_char, "Move to next occurance of char",
|
|
|
|
extend_till_prev_char,
|
|
|
|
extend_till_char, "Extend till next occurance of char",
|
|
|
|
extend_prev_char,
|
|
|
|
extend_next_char, "Extend to next occurance of char",
|
|
|
|
replace,
|
|
|
|
till_prev_char, "Move till previous occurance of char",
|
|
|
|
switch_case,
|
|
|
|
find_prev_char, "Move to previous occurance of char",
|
|
|
|
switch_to_uppercase,
|
|
|
|
extend_till_prev_char, "Extend till previous occurance of char",
|
|
|
|
switch_to_lowercase,
|
|
|
|
extend_prev_char, "Extend to previous occurance of char",
|
|
|
|
page_up,
|
|
|
|
replace, "Replace with new char",
|
|
|
|
page_down,
|
|
|
|
switch_case, "Switch (toggle) case",
|
|
|
|
half_page_up,
|
|
|
|
switch_to_uppercase, "Switch to uppercase",
|
|
|
|
half_page_down,
|
|
|
|
switch_to_lowercase, "Switch to lowercase",
|
|
|
|
extend_char_left,
|
|
|
|
page_up, "Move page up",
|
|
|
|
extend_char_right,
|
|
|
|
page_down, "Move page down",
|
|
|
|
extend_line_up,
|
|
|
|
half_page_up, "Move half page up",
|
|
|
|
extend_line_down,
|
|
|
|
half_page_down, "Move half page down",
|
|
|
|
select_all,
|
|
|
|
select_all, "Select whole document",
|
|
|
|
select_regex,
|
|
|
|
select_regex, "Select all regex matches inside selections",
|
|
|
|
split_selection,
|
|
|
|
split_selection, "Split selection into subselections on regex matches",
|
|
|
|
split_selection_on_newline,
|
|
|
|
split_selection_on_newline, "Split selection on newlines",
|
|
|
|
search,
|
|
|
|
search, "Search for regex pattern",
|
|
|
|
search_next,
|
|
|
|
search_next, "Select next search match",
|
|
|
|
extend_search_next,
|
|
|
|
extend_search_next, "Add next search match to selection",
|
|
|
|
search_selection,
|
|
|
|
search_selection, "Use current selection as search pattern",
|
|
|
|
extend_line,
|
|
|
|
extend_line, "Select current line, if already selected, extend to next line",
|
|
|
|
extend_to_line_bounds,
|
|
|
|
extend_to_line_bounds, "Extend selection to line bounds (line-wise selection)",
|
|
|
|
delete_selection,
|
|
|
|
delete_selection, "Delete selection",
|
|
|
|
change_selection,
|
|
|
|
change_selection, "Change selection (delete and enter insert mode)",
|
|
|
|
collapse_selection,
|
|
|
|
collapse_selection, "Collapse selection onto a single cursor",
|
|
|
|
flip_selections,
|
|
|
|
flip_selections, "Flip selection cursor and anchor",
|
|
|
|
insert_mode,
|
|
|
|
insert_mode, "Insert before selection",
|
|
|
|
append_mode,
|
|
|
|
append_mode, "Insert after selection (append)",
|
|
|
|
command_mode,
|
|
|
|
command_mode, "Enter command mode",
|
|
|
|
file_picker,
|
|
|
|
file_picker, "Open file picker",
|
|
|
|
buffer_picker,
|
|
|
|
code_action, "Perform code action",
|
|
|
|
symbol_picker,
|
|
|
|
buffer_picker, "Open buffer picker",
|
|
|
|
last_picker,
|
|
|
|
symbol_picker, "Open symbol picker",
|
|
|
|
prepend_to_line,
|
|
|
|
last_picker, "Open last picker",
|
|
|
|
append_to_line,
|
|
|
|
prepend_to_line, "Insert at start of line",
|
|
|
|
open_below,
|
|
|
|
append_to_line, "Insert at end of line",
|
|
|
|
open_above,
|
|
|
|
open_below, "Open new line below selection",
|
|
|
|
normal_mode,
|
|
|
|
open_above, "Open new line above selection",
|
|
|
|
goto_mode,
|
|
|
|
normal_mode, "Enter normal mode",
|
|
|
|
select_mode,
|
|
|
|
select_mode, "Enter selection extend mode",
|
|
|
|
exit_select_mode,
|
|
|
|
exit_select_mode, "Exit selection mode",
|
|
|
|
goto_definition,
|
|
|
|
goto_definition, "Goto definition",
|
|
|
|
goto_type_definition,
|
|
|
|
goto_type_definition, "Goto type definition",
|
|
|
|
goto_implementation,
|
|
|
|
goto_implementation, "Goto implementation",
|
|
|
|
goto_file_start,
|
|
|
|
goto_file_start, "Goto file start",
|
|
|
|
goto_file_end,
|
|
|
|
goto_file_end, "Goto file end",
|
|
|
|
goto_reference,
|
|
|
|
goto_reference, "Goto references",
|
|
|
|
goto_first_diag,
|
|
|
|
goto_window_top, "Goto window top",
|
|
|
|
goto_last_diag,
|
|
|
|
goto_window_middle, "Goto window middle",
|
|
|
|
goto_next_diag,
|
|
|
|
goto_window_bottom, "Goto window bottom",
|
|
|
|
goto_prev_diag,
|
|
|
|
goto_last_accessed_file, "Goto last accessed file",
|
|
|
|
goto_line_start,
|
|
|
|
goto_first_diag, "Goto first diagnostic",
|
|
|
|
goto_line_end,
|
|
|
|
goto_last_diag, "Goto last diagnostic",
|
|
|
|
goto_line_end_newline,
|
|
|
|
goto_next_diag, "Goto next diagnostic",
|
|
|
|
goto_first_nonwhitespace,
|
|
|
|
goto_prev_diag, "Goto previous diagnostic",
|
|
|
|
signature_help,
|
|
|
|
goto_line_start, "Goto line start",
|
|
|
|
insert_tab,
|
|
|
|
goto_line_end, "Goto line end",
|
|
|
|
insert_newline,
|
|
|
|
// TODO: different description ?
|
|
|
|
delete_char_backward,
|
|
|
|
goto_line_end_newline, "Goto line end",
|
|
|
|
delete_char_forward,
|
|
|
|
goto_first_nonwhitespace, "Goto first non-blank in line",
|
|
|
|
delete_word_backward,
|
|
|
|
signature_help, "Show signature help",
|
|
|
|
undo,
|
|
|
|
insert_tab, "Insert tab char",
|
|
|
|
redo,
|
|
|
|
insert_newline, "Insert newline char",
|
|
|
|
yank,
|
|
|
|
delete_char_backward, "Delete previous char",
|
|
|
|
yank_joined_to_clipboard,
|
|
|
|
delete_char_forward, "Delete next char",
|
|
|
|
yank_main_selection_to_clipboard,
|
|
|
|
delete_word_backward, "Delete previous word",
|
|
|
|
replace_with_yanked,
|
|
|
|
undo, "Undo change",
|
|
|
|
replace_selections_with_clipboard,
|
|
|
|
redo, "Redo change",
|
|
|
|
paste_after,
|
|
|
|
yank, "Yank selection",
|
|
|
|
paste_before,
|
|
|
|
yank_joined_to_clipboard, "Join and yank selections to clipboard",
|
|
|
|
paste_clipboard_after,
|
|
|
|
yank_main_selection_to_clipboard, "Yank main selection to clipboard",
|
|
|
|
paste_clipboard_before,
|
|
|
|
replace_with_yanked, "Replace with yanked text",
|
|
|
|
indent,
|
|
|
|
replace_selections_with_clipboard, "Replace selections by clipboard content",
|
|
|
|
unindent,
|
|
|
|
paste_after, "Paste after selection",
|
|
|
|
format_selections,
|
|
|
|
paste_before, "Paste before selection",
|
|
|
|
join_selections,
|
|
|
|
paste_clipboard_after, "Paste clipboard after selections",
|
|
|
|
keep_selections,
|
|
|
|
paste_clipboard_before, "Paste clipboard before selections",
|
|
|
|
keep_primary_selection,
|
|
|
|
indent, "Indent selection",
|
|
|
|
completion,
|
|
|
|
unindent, "Unindent selection",
|
|
|
|
hover,
|
|
|
|
format_selections, "Format selection",
|
|
|
|
toggle_comments,
|
|
|
|
join_selections, "Join lines inside selection",
|
|
|
|
expand_selection,
|
|
|
|
keep_selections, "Keep selections matching regex",
|
|
|
|
match_brackets,
|
|
|
|
keep_primary_selection, "Keep primary selection",
|
|
|
|
jump_forward,
|
|
|
|
completion, "Invoke completion popup",
|
|
|
|
jump_backward,
|
|
|
|
hover, "Show docs for item under cursor",
|
|
|
|
window_mode,
|
|
|
|
toggle_comments, "Comment/uncomment selections",
|
|
|
|
rotate_view,
|
|
|
|
expand_selection, "Expand selection to parent syntax node",
|
|
|
|
hsplit,
|
|
|
|
jump_forward, "Jump forward on jumplist",
|
|
|
|
vsplit,
|
|
|
|
jump_backward, "Jump backward on jumplist",
|
|
|
|
wclose,
|
|
|
|
rotate_view, "Goto next window",
|
|
|
|
select_register,
|
|
|
|
hsplit, "Horizontal bottom split",
|
|
|
|
space_mode,
|
|
|
|
vsplit, "Vertical right split",
|
|
|
|
view_mode,
|
|
|
|
wclose, "Close window",
|
|
|
|
left_bracket_mode,
|
|
|
|
select_register, "Select register",
|
|
|
|
right_bracket_mode,
|
|
|
|
align_view_middle, "Align view middle",
|
|
|
|
match_mode
|
|
|
|
align_view_top, "Align view top",
|
|
|
|
|
|
|
|
align_view_center, "Align view center",
|
|
|
|
|
|
|
|
align_view_bottom, "Align view bottom",
|
|
|
|
|
|
|
|
scroll_up, "Scroll view up",
|
|
|
|
|
|
|
|
scroll_down, "Scroll view down",
|
|
|
|
|
|
|
|
match_brackets, "Goto matching bracket",
|
|
|
|
|
|
|
|
surround_add, "Surround add",
|
|
|
|
|
|
|
|
surround_replace, "Surround replace",
|
|
|
|
|
|
|
|
surround_delete, "Surround delete",
|
|
|
|
|
|
|
|
select_textobject_around, "Select around object",
|
|
|
|
|
|
|
|
select_textobject_inner, "Select inside object"
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for Command {
|
|
|
|
impl fmt::Debug for Command {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
let Command(name, _) = self;
|
|
|
|
let Command { name, .. } = self;
|
|
|
|
f.debug_tuple("Command").field(name).finish()
|
|
|
|
f.debug_tuple("Command").field(name).finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for Command {
|
|
|
|
impl fmt::Display for Command {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
let Command(name, _) = self;
|
|
|
|
let Command { name, .. } = self;
|
|
|
|
f.write_str(name)
|
|
|
|
f.write_str(name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -305,7 +314,7 @@ impl std::str::FromStr for Command {
|
|
|
|
Command::COMMAND_LIST
|
|
|
|
Command::COMMAND_LIST
|
|
|
|
.iter()
|
|
|
|
.iter()
|
|
|
|
.copied()
|
|
|
|
.copied()
|
|
|
|
.find(|cmd| cmd.0 == s)
|
|
|
|
.find(|cmd| cmd.name == s)
|
|
|
|
.ok_or_else(|| anyhow!("No command named '{}'", s))
|
|
|
|
.ok_or_else(|| anyhow!("No command named '{}'", s))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1171,7 +1180,7 @@ fn delete_selection(cx: &mut Context) {
|
|
|
|
let reg_name = cx.selected_register.name();
|
|
|
|
let reg_name = cx.selected_register.name();
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let registers = &mut cx.editor.registers;
|
|
|
|
let registers = &mut cx.editor.registers;
|
|
|
|
let reg = registers.get_or_insert(reg_name);
|
|
|
|
let reg = registers.get_mut(reg_name);
|
|
|
|
delete_selection_impl(reg, doc, view.id);
|
|
|
|
delete_selection_impl(reg, doc, view.id);
|
|
|
|
|
|
|
|
|
|
|
|
doc.append_changes_to_history(view.id);
|
|
|
|
doc.append_changes_to_history(view.id);
|
|
|
@ -1184,7 +1193,7 @@ fn change_selection(cx: &mut Context) {
|
|
|
|
let reg_name = cx.selected_register.name();
|
|
|
|
let reg_name = cx.selected_register.name();
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
let registers = &mut cx.editor.registers;
|
|
|
|
let registers = &mut cx.editor.registers;
|
|
|
|
let reg = registers.get_or_insert(reg_name);
|
|
|
|
let reg = registers.get_mut(reg_name);
|
|
|
|
delete_selection_impl(reg, doc, view.id);
|
|
|
|
delete_selection_impl(reg, doc, view.id);
|
|
|
|
enter_insert_mode(doc);
|
|
|
|
enter_insert_mode(doc);
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -1974,6 +1983,7 @@ mod cmd {
|
|
|
|
fn command_mode(cx: &mut Context) {
|
|
|
|
fn command_mode(cx: &mut Context) {
|
|
|
|
let mut prompt = Prompt::new(
|
|
|
|
let mut prompt = Prompt::new(
|
|
|
|
":".to_owned(),
|
|
|
|
":".to_owned(),
|
|
|
|
|
|
|
|
Some(':'),
|
|
|
|
|input: &str| {
|
|
|
|
|input: &str| {
|
|
|
|
// we use .this over split_whitespace() because we care about empty segments
|
|
|
|
// we use .this over split_whitespace() because we care about empty segments
|
|
|
|
let parts = input.split(' ').collect::<Vec<&str>>();
|
|
|
|
let parts = input.split(' ').collect::<Vec<&str>>();
|
|
|
@ -2147,6 +2157,112 @@ fn symbol_picker(cx: &mut Context) {
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pub fn code_action(cx: &mut Context) {
|
|
|
|
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let language_server = match doc.language_server() {
|
|
|
|
|
|
|
|
Some(language_server) => language_server,
|
|
|
|
|
|
|
|
None => return,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let range = range_to_lsp_range(
|
|
|
|
|
|
|
|
doc.text(),
|
|
|
|
|
|
|
|
doc.selection(view.id).primary(),
|
|
|
|
|
|
|
|
language_server.offset_encoding(),
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let future = language_server.code_actions(doc.identifier(), range);
|
|
|
|
|
|
|
|
let offset_encoding = language_server.offset_encoding();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cx.callback(
|
|
|
|
|
|
|
|
future,
|
|
|
|
|
|
|
|
move |_editor: &mut Editor,
|
|
|
|
|
|
|
|
compositor: &mut Compositor,
|
|
|
|
|
|
|
|
response: Option<lsp::CodeActionResponse>| {
|
|
|
|
|
|
|
|
if let Some(actions) = response {
|
|
|
|
|
|
|
|
let picker = Picker::new(
|
|
|
|
|
|
|
|
actions,
|
|
|
|
|
|
|
|
|action| match action {
|
|
|
|
|
|
|
|
lsp::CodeActionOrCommand::CodeAction(action) => {
|
|
|
|
|
|
|
|
action.title.as_str().into()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lsp::CodeActionOrCommand::Command(command) => command.title.as_str().into(),
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
move |editor, code_action, _action| match code_action {
|
|
|
|
|
|
|
|
lsp::CodeActionOrCommand::Command(command) => {
|
|
|
|
|
|
|
|
log::debug!("code action command: {:?}", command);
|
|
|
|
|
|
|
|
editor.set_error(String::from("Handling code action command is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lsp::CodeActionOrCommand::CodeAction(code_action) => {
|
|
|
|
|
|
|
|
log::debug!("code action: {:?}", code_action);
|
|
|
|
|
|
|
|
if let Some(ref workspace_edit) = code_action.edit {
|
|
|
|
|
|
|
|
apply_workspace_edit(editor, offset_encoding, workspace_edit)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
compositor.push(Box::new(picker))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn apply_workspace_edit(
|
|
|
|
|
|
|
|
editor: &mut Editor,
|
|
|
|
|
|
|
|
offset_encoding: OffsetEncoding,
|
|
|
|
|
|
|
|
workspace_edit: &lsp::WorkspaceEdit,
|
|
|
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
if let Some(ref changes) = workspace_edit.changes {
|
|
|
|
|
|
|
|
log::debug!("workspace changes: {:?}", changes);
|
|
|
|
|
|
|
|
editor.set_error(String::from("Handling workspace changesis not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
|
|
|
|
|
|
|
|
// TODO: find some example that uses workspace changes, and test it
|
|
|
|
|
|
|
|
// for (url, edits) in changes.iter() {
|
|
|
|
|
|
|
|
// let file_path = url.origin().ascii_serialization();
|
|
|
|
|
|
|
|
// let file_path = std::path::PathBuf::from(file_path);
|
|
|
|
|
|
|
|
// let file = std::fs::File::open(file_path).unwrap();
|
|
|
|
|
|
|
|
// let mut text = Rope::from_reader(file).unwrap();
|
|
|
|
|
|
|
|
// let transaction = edits_to_changes(&text, edits);
|
|
|
|
|
|
|
|
// transaction.apply(&mut text);
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(ref document_changes) = workspace_edit.document_changes {
|
|
|
|
|
|
|
|
match document_changes {
|
|
|
|
|
|
|
|
lsp::DocumentChanges::Edits(document_edits) => {
|
|
|
|
|
|
|
|
for document_edit in document_edits {
|
|
|
|
|
|
|
|
let (view, doc) = current!(editor);
|
|
|
|
|
|
|
|
assert_eq!(doc.url().unwrap(), document_edit.text_document.uri);
|
|
|
|
|
|
|
|
let edits = document_edit
|
|
|
|
|
|
|
|
.edits
|
|
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
|
|
.map(|edit| match edit {
|
|
|
|
|
|
|
|
lsp::OneOf::Left(text_edit) => text_edit,
|
|
|
|
|
|
|
|
lsp::OneOf::Right(annotated_text_edit) => {
|
|
|
|
|
|
|
|
&annotated_text_edit.text_edit
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
.cloned()
|
|
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let transaction = helix_lsp::util::generate_transaction_from_edits(
|
|
|
|
|
|
|
|
doc.text(),
|
|
|
|
|
|
|
|
edits,
|
|
|
|
|
|
|
|
offset_encoding,
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
doc.apply(&transaction, view.id);
|
|
|
|
|
|
|
|
doc.append_changes_to_history(view.id);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
lsp::DocumentChanges::Operations(operations) => {
|
|
|
|
|
|
|
|
log::debug!("document changes - operations: {:?}", operations);
|
|
|
|
|
|
|
|
editor.set_error(String::from("Handling document operations is not implemented yet, see https://github.com/helix-editor/helix/issues/183"));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn last_picker(cx: &mut Context) {
|
|
|
|
fn last_picker(cx: &mut Context) {
|
|
|
|
// TODO: last picker does not seemed to work well with buffer_picker
|
|
|
|
// TODO: last picker does not seemed to work well with buffer_picker
|
|
|
|
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
|
|
|
|
cx.callback = Some(Box::new(|compositor: &mut Compositor| {
|
|
|
@ -2360,20 +2476,6 @@ fn exit_select_mode(cx: &mut Context) {
|
|
|
|
doc_mut!(cx.editor).mode = Mode::Normal;
|
|
|
|
doc_mut!(cx.editor).mode = Mode::Normal;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn goto_prehook(cx: &mut Context) -> bool {
|
|
|
|
|
|
|
|
if let Some(count) = cx.count {
|
|
|
|
|
|
|
|
push_jump(cx.editor);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let (view, doc) = current!(cx.editor);
|
|
|
|
|
|
|
|
let line_idx = std::cmp::min(count.get() - 1, doc.text().len_lines().saturating_sub(1));
|
|
|
|
|
|
|
|
let pos = doc.text().line_to_char(line_idx);
|
|
|
|
|
|
|
|
doc.set_selection(view.id, Selection::point(pos));
|
|
|
|
|
|
|
|
true
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn goto_impl(
|
|
|
|
fn goto_impl(
|
|
|
|
editor: &mut Editor,
|
|
|
|
editor: &mut Editor,
|
|
|
|
compositor: &mut Compositor,
|
|
|
|
compositor: &mut Compositor,
|
|
|
@ -3823,199 +3925,3 @@ fn surround_delete(cx: &mut Context) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Do nothing, just for modeinfo.
|
|
|
|
|
|
|
|
fn noop(_cx: &mut Context) -> bool {
|
|
|
|
|
|
|
|
false
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Generate modeinfo.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// If prehook returns true then it will stop the rest.
|
|
|
|
|
|
|
|
macro_rules! mode_info {
|
|
|
|
|
|
|
|
// TODO: reuse $mode for $stat
|
|
|
|
|
|
|
|
(@join $first:expr $(,$rest:expr)*) => {
|
|
|
|
|
|
|
|
concat!($first, $(", ", $rest),*)
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
(@name #[doc = $name:literal] $(#[$rest:meta])*) => {
|
|
|
|
|
|
|
|
$name
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
#[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident,
|
|
|
|
|
|
|
|
$(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
|
|
|
|
|
|
|
|
} => {
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
#[doc = $name]
|
|
|
|
|
|
|
|
$(#[$doc])*
|
|
|
|
|
|
|
|
$mode, $stat, noop,
|
|
|
|
|
|
|
|
$(
|
|
|
|
|
|
|
|
#[doc = $desc]
|
|
|
|
|
|
|
|
$($key)|+ => $func
|
|
|
|
|
|
|
|
),+,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
#[doc = $name:literal] $(#[$doc:meta])* $mode:ident, $stat:ident, $prehook:expr,
|
|
|
|
|
|
|
|
$(#[doc = $desc:literal] $($key:tt)|+ => $func:expr),+,
|
|
|
|
|
|
|
|
} => {
|
|
|
|
|
|
|
|
#[doc = $name]
|
|
|
|
|
|
|
|
$(#[$doc])*
|
|
|
|
|
|
|
|
#[doc = ""]
|
|
|
|
|
|
|
|
#[doc = "<table><tr><th>key</th><th>desc</th></tr><tbody>"]
|
|
|
|
|
|
|
|
$(
|
|
|
|
|
|
|
|
#[doc = "<tr><td>"]
|
|
|
|
|
|
|
|
// TODO switch to this once we use rust 1.54
|
|
|
|
|
|
|
|
// right now it will produce multiple rows
|
|
|
|
|
|
|
|
// #[doc = mode_info!(@join $($key),+)]
|
|
|
|
|
|
|
|
$(
|
|
|
|
|
|
|
|
#[doc = $key]
|
|
|
|
|
|
|
|
)+
|
|
|
|
|
|
|
|
// <-
|
|
|
|
|
|
|
|
#[doc = "</td><td>"]
|
|
|
|
|
|
|
|
#[doc = $desc]
|
|
|
|
|
|
|
|
#[doc = "</td></tr>"]
|
|
|
|
|
|
|
|
)+
|
|
|
|
|
|
|
|
#[doc = "</tbody></table>"]
|
|
|
|
|
|
|
|
pub fn $mode(cx: &mut Context) {
|
|
|
|
|
|
|
|
if $prehook(cx) {
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
static $stat: OnceCell<Info> = OnceCell::new();
|
|
|
|
|
|
|
|
cx.editor.autoinfo = Some($stat.get_or_init(|| Info::key(
|
|
|
|
|
|
|
|
$name.trim(),
|
|
|
|
|
|
|
|
vec![$((&[$($key.parse().unwrap()),+], $desc)),+],
|
|
|
|
|
|
|
|
)));
|
|
|
|
|
|
|
|
use helix_core::hashmap;
|
|
|
|
|
|
|
|
// TODO: try and convert this to match later
|
|
|
|
|
|
|
|
let map = hashmap! {
|
|
|
|
|
|
|
|
$($($key.parse::<KeyEvent>().unwrap() => $func as for<'r, 's> fn(&'r mut Context<'s>)),+),*
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
cx.on_next_key_mode(map);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// space mode
|
|
|
|
|
|
|
|
space_mode, SPACE_MODE,
|
|
|
|
|
|
|
|
/// resume last picker
|
|
|
|
|
|
|
|
"'" => last_picker,
|
|
|
|
|
|
|
|
/// file picker
|
|
|
|
|
|
|
|
"f" => file_picker,
|
|
|
|
|
|
|
|
/// buffer picker
|
|
|
|
|
|
|
|
"b" => buffer_picker,
|
|
|
|
|
|
|
|
/// symbol picker
|
|
|
|
|
|
|
|
"s" => symbol_picker,
|
|
|
|
|
|
|
|
/// window mode
|
|
|
|
|
|
|
|
"w" => window_mode,
|
|
|
|
|
|
|
|
/// yank joined to clipboard
|
|
|
|
|
|
|
|
"y" => yank_joined_to_clipboard,
|
|
|
|
|
|
|
|
/// yank main selection to clipboard
|
|
|
|
|
|
|
|
"Y" => yank_main_selection_to_clipboard,
|
|
|
|
|
|
|
|
/// paste system clipboard after selections
|
|
|
|
|
|
|
|
"p" => paste_clipboard_after,
|
|
|
|
|
|
|
|
/// paste system clipboard before selections
|
|
|
|
|
|
|
|
"P" => paste_clipboard_before,
|
|
|
|
|
|
|
|
/// replace selections with clipboard
|
|
|
|
|
|
|
|
"R" => replace_selections_with_clipboard,
|
|
|
|
|
|
|
|
/// keep primary selection
|
|
|
|
|
|
|
|
"space" => keep_primary_selection,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// goto
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// When specified with a count, it will go to that line without entering the mode.
|
|
|
|
|
|
|
|
goto_mode, GOTO_MODE, goto_prehook,
|
|
|
|
|
|
|
|
/// file start
|
|
|
|
|
|
|
|
"g" => goto_file_start,
|
|
|
|
|
|
|
|
/// file end
|
|
|
|
|
|
|
|
"e" => goto_file_end,
|
|
|
|
|
|
|
|
/// line start
|
|
|
|
|
|
|
|
"h" => goto_line_start,
|
|
|
|
|
|
|
|
/// line end
|
|
|
|
|
|
|
|
"l" => goto_line_end,
|
|
|
|
|
|
|
|
/// line first non blank
|
|
|
|
|
|
|
|
"s" => goto_first_nonwhitespace,
|
|
|
|
|
|
|
|
/// definition
|
|
|
|
|
|
|
|
"d" => goto_definition,
|
|
|
|
|
|
|
|
/// type references
|
|
|
|
|
|
|
|
"y" => goto_type_definition,
|
|
|
|
|
|
|
|
/// references
|
|
|
|
|
|
|
|
"r" => goto_reference,
|
|
|
|
|
|
|
|
/// implementation
|
|
|
|
|
|
|
|
"i" => goto_implementation,
|
|
|
|
|
|
|
|
/// window top
|
|
|
|
|
|
|
|
"t" => goto_window_top,
|
|
|
|
|
|
|
|
/// window middle
|
|
|
|
|
|
|
|
"m" => goto_window_middle,
|
|
|
|
|
|
|
|
/// window bottom
|
|
|
|
|
|
|
|
"b" => goto_window_bottom,
|
|
|
|
|
|
|
|
/// last accessed file
|
|
|
|
|
|
|
|
"a" => goto_last_accessed_file,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// window
|
|
|
|
|
|
|
|
window_mode, WINDOW_MODE,
|
|
|
|
|
|
|
|
/// rotate
|
|
|
|
|
|
|
|
"w" | "C-w" => rotate_view,
|
|
|
|
|
|
|
|
/// horizontal split
|
|
|
|
|
|
|
|
"h" => hsplit,
|
|
|
|
|
|
|
|
/// vertical split
|
|
|
|
|
|
|
|
"v" => vsplit,
|
|
|
|
|
|
|
|
/// close
|
|
|
|
|
|
|
|
"q" => wclose,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// match
|
|
|
|
|
|
|
|
match_mode, MATCH_MODE,
|
|
|
|
|
|
|
|
/// matching character
|
|
|
|
|
|
|
|
"m" => match_brackets,
|
|
|
|
|
|
|
|
/// surround add
|
|
|
|
|
|
|
|
"s" => surround_add,
|
|
|
|
|
|
|
|
/// surround replace
|
|
|
|
|
|
|
|
"r" => surround_replace,
|
|
|
|
|
|
|
|
/// surround delete
|
|
|
|
|
|
|
|
"d" => surround_delete,
|
|
|
|
|
|
|
|
/// around object
|
|
|
|
|
|
|
|
"a" => select_textobject_around,
|
|
|
|
|
|
|
|
/// inside object
|
|
|
|
|
|
|
|
"i" => select_textobject_inner,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// select to previous
|
|
|
|
|
|
|
|
left_bracket_mode, LEFT_BRACKET_MODE,
|
|
|
|
|
|
|
|
/// previous diagnostic
|
|
|
|
|
|
|
|
"d" => goto_prev_diag,
|
|
|
|
|
|
|
|
/// diagnostic (first)
|
|
|
|
|
|
|
|
"D" => goto_first_diag,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// select to next
|
|
|
|
|
|
|
|
right_bracket_mode, RIGHT_BRACKET_MODE,
|
|
|
|
|
|
|
|
/// diagnostic
|
|
|
|
|
|
|
|
"d" => goto_next_diag,
|
|
|
|
|
|
|
|
/// diagnostic (last)
|
|
|
|
|
|
|
|
"D" => goto_last_diag,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mode_info! {
|
|
|
|
|
|
|
|
/// view
|
|
|
|
|
|
|
|
view_mode, VIEW_MODE,
|
|
|
|
|
|
|
|
/// align view top
|
|
|
|
|
|
|
|
"t" => align_view_top,
|
|
|
|
|
|
|
|
/// align view center
|
|
|
|
|
|
|
|
"z" | "c" => align_view_center,
|
|
|
|
|
|
|
|
/// align view bottom
|
|
|
|
|
|
|
|
"b" => align_view_bottom,
|
|
|
|
|
|
|
|
/// align view middle
|
|
|
|
|
|
|
|
"m" => align_view_middle,
|
|
|
|
|
|
|
|
/// scroll up
|
|
|
|
|
|
|
|
"k" => scroll_up,
|
|
|
|
|
|
|
|
/// scroll down
|
|
|
|
|
|
|
|
"j" => scroll_down,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|