diff options
Diffstat (limited to 'helix-term/src/commands.rs')
-rw-r--r-- | helix-term/src/commands.rs | 146 |
1 files changed, 117 insertions, 29 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 5c26a5b2..6123b7d2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -11,6 +11,7 @@ use helix_core::{ object, pos_at_coords, regex::{self, Regex, RegexBuilder}, search, selection, shellwords, surround, textobject, + tree_sitter::Node, unicode::width::UnicodeWidthChar, LineEnding, Position, Range, Rope, RopeGraphemes, RopeSlice, Selection, SmallVec, Tendril, Transaction, @@ -173,7 +174,7 @@ macro_rules! static_commands { impl MappableCommand { pub fn execute(&self, cx: &mut Context) { match &self { - MappableCommand::Typable { name, args, doc: _ } => { + Self::Typable { name, args, doc: _ } => { let args: Vec<Cow<str>> = args.iter().map(Cow::from).collect(); if let Some(command) = cmd::TYPABLE_COMMAND_MAP.get(name.as_str()) { let mut cx = compositor::Context { @@ -186,21 +187,21 @@ impl MappableCommand { } } } - MappableCommand::Static { fun, .. } => (fun)(cx), + Self::Static { fun, .. } => (fun)(cx), } } pub fn name(&self) -> &str { match &self { - MappableCommand::Typable { name, .. } => name, - MappableCommand::Static { name, .. } => name, + Self::Typable { name, .. } => name, + Self::Static { name, .. } => name, } } pub fn doc(&self) -> &str { match &self { - MappableCommand::Typable { doc, .. } => doc, - MappableCommand::Static { doc, .. } => doc, + Self::Typable { doc, .. } => doc, + Self::Static { doc, .. } => doc, } } @@ -363,6 +364,8 @@ impl MappableCommand { rotate_selection_contents_backward, "Rotate selections contents backward", expand_selection, "Expand selection to parent syntax node", shrink_selection, "Shrink selection to previously expanded syntax node", + select_next_sibling, "Select the next sibling in the syntax tree", + select_prev_sibling, "Select the previous sibling in the syntax tree", jump_forward, "Jump forward on jumplist", jump_backward, "Jump backward on jumplist", save_selection, "Save the current selection to the jumplist", @@ -2278,7 +2281,7 @@ pub mod cmd { force: bool, ) -> anyhow::Result<()> { let mut errors = String::new(); - + let jobs = &mut cx.jobs; // save all documents for doc in &mut cx.editor.documents.values_mut() { if doc.path().is_none() { @@ -2286,9 +2289,23 @@ pub mod cmd { continue; } - // TODO: handle error. - let handle = doc.save(); - cx.jobs.add(Job::new(handle).wait_before_exiting()); + if !doc.is_modified() { + continue; + } + + let fmt = doc.auto_format().map(|fmt| { + let shared = fmt.shared(); + let callback = make_format_callback( + doc.id(), + doc.version(), + Modified::SetUnmodified, + shared.clone(), + ); + jobs.callback(callback); + shared + }); + let future = doc.format_and_save(fmt); + jobs.add(Job::new(future).wait_before_exiting()); } if quit { @@ -2748,6 +2765,46 @@ pub mod cmd { Ok(()) } + fn tree_sitter_subtree( + cx: &mut compositor::Context, + _args: &[Cow<str>], + _event: PromptEvent, + ) -> anyhow::Result<()> { + let (view, doc) = current!(cx.editor); + + if let Some(syntax) = doc.syntax() { + let primary_selection = doc.selection(view.id).primary(); + let text = doc.text(); + let from = text.char_to_byte(primary_selection.from()); + let to = text.char_to_byte(primary_selection.to()); + if let Some(selected_node) = syntax + .tree() + .root_node() + .descendant_for_byte_range(from, to) + { + let contents = format!("```tsq\n{}\n```", selected_node.to_sexp()); + + let callback = async move { + let call: job::Callback = + Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { + let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); + let popup = Popup::new("hover", contents); + if let Some(doc_popup) = compositor.find_id("hover") { + *doc_popup = popup; + } else { + compositor.push(Box::new(popup)); + } + }); + Ok(call) + }; + + cx.jobs.callback(callback); + } + } + + Ok(()) + } + pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[ TypableCommand { name: "quit", @@ -3064,6 +3121,13 @@ pub mod cmd { fun: sort_reverse, completer: None, }, + TypableCommand { + name: "tree-sitter-subtree", + aliases: &["ts-subtree"], + doc: "Display tree sitter subtree under cursor, primarily for debugging queries.", + fun: tree_sitter_subtree, + completer: None, + }, ]; pub static TYPABLE_COMMAND_MAP: Lazy<HashMap<&'static str, &'static TypableCommand>> = @@ -3239,8 +3303,8 @@ fn buffer_picker(cx: &mut Context) { .map(|(_, doc)| new_meta(doc)) .collect(), BufferMeta::format, - |editor: &mut Editor, meta, _action| { - editor.switch(meta.id, Action::Replace); + |editor: &mut Editor, meta, action| { + editor.switch(meta.id, action); }, |editor, meta| { let doc = &editor.documents.get(&meta.id)?; @@ -3479,11 +3543,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { match op { ResourceOp::Create(op) => { let path = op.uri.to_file_path().unwrap(); - let ignore_if_exists = if let Some(options) = &op.options { + let ignore_if_exists = op.options.as_ref().map_or(false, |options| { !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - } else { - false - }; + }); if ignore_if_exists && path.exists() { Ok(()) } else { @@ -3493,11 +3555,12 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { ResourceOp::Delete(op) => { let path = op.uri.to_file_path().unwrap(); if path.is_dir() { - let recursive = if let Some(options) = &op.options { - options.recursive.unwrap_or(false) - } else { - false - }; + let recursive = op + .options + .as_ref() + .and_then(|options| options.recursive) + .unwrap_or(false); + if recursive { fs::remove_dir_all(&path) } else { @@ -3512,11 +3575,9 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> { ResourceOp::Rename(op) => { let from = op.old_uri.to_file_path().unwrap(); let to = op.new_uri.to_file_path().unwrap(); - let ignore_if_exists = if let Some(options) = &op.options { + let ignore_if_exists = op.options.as_ref().map_or(false, |options| { !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false) - } else { - false - }; + }); if ignore_if_exists && to.exists() { Ok(()) } else { @@ -5388,8 +5449,8 @@ fn hover(cx: &mut Context) { // skip if contents empty let contents = ui::Markdown::new(contents, editor.syn_loader.clone()); - let popup = Popup::new("documentation", contents); - if let Some(doc_popup) = compositor.find_id("documentation") { + let popup = Popup::new("hover", contents); + if let Some(doc_popup) = compositor.find_id("hover") { *doc_popup = popup; } else { compositor.push(Box::new(popup)); @@ -5490,7 +5551,7 @@ fn expand_selection(cx: &mut Context) { // save current selection so it can be restored using shrink_selection view.object_selections.push(current_selection.clone()); - let selection = object::expand_selection(syntax, text, current_selection); + let selection = object::expand_selection(syntax, text, current_selection.clone()); doc.set_selection(view.id, selection); } }; @@ -5516,7 +5577,26 @@ fn shrink_selection(cx: &mut Context) { // if not previous selection, shrink to first child if let Some(syntax) = doc.syntax() { let text = doc.text().slice(..); - let selection = object::shrink_selection(syntax, text, current_selection); + let selection = object::shrink_selection(syntax, text, current_selection.clone()); + doc.set_selection(view.id, selection); + } + }; + motion(cx.editor); + cx.editor.last_motion = Some(Motion(Box::new(motion))); +} + +fn select_sibling_impl<F>(cx: &mut Context, sibling_fn: &'static F) +where + F: Fn(Node) -> Option<Node>, +{ + let motion = |editor: &mut Editor| { + let (view, doc) = current!(editor); + + if let Some(syntax) = doc.syntax() { + let text = doc.text().slice(..); + let current_selection = doc.selection(view.id); + let selection = + object::select_sibling(syntax, text, current_selection.clone(), sibling_fn); doc.set_selection(view.id, selection); } }; @@ -5524,6 +5604,14 @@ fn shrink_selection(cx: &mut Context) { cx.editor.last_motion = Some(Motion(Box::new(motion))); } +fn select_next_sibling(cx: &mut Context) { + select_sibling_impl(cx, &|node| Node::next_sibling(&node)) +} + +fn select_prev_sibling(cx: &mut Context) { + select_sibling_impl(cx, &|node| Node::prev_sibling(&node)) +} + fn match_brackets(cx: &mut Context) { let (view, doc) = current!(cx.editor); |