diff --git a/crates/vim/src/object.rs b/crates/vim/src/object.rs index 7ce3dbe4c68060d7329c27c773cad5c7bc187d83..d49f6f553807df45debfbd40d2f7402bcebc5197 100644 --- a/crates/vim/src/object.rs +++ b/crates/vim/src/object.rs @@ -37,6 +37,7 @@ pub enum Object { SquareBrackets, CurlyBrackets, AngleBrackets, + SyntaxNode, Argument, IndentObj { include_below: bool }, Tag, @@ -276,7 +277,10 @@ actions!( Method, Class, Comment, - EntireFile + /// Selects the entire file. + EntireFile, + /// Selects a syntax node. + SyntaxNode, ] ); @@ -368,6 +372,9 @@ pub fn register(editor: &mut Editor, cx: &mut Context) { vim.object(Object::IndentObj { include_below }, window, cx) }, ); + Vim::action(editor, cx, |vim, _: &SyntaxNode, window, cx| { + vim.object(Object::SyntaxNode, window, cx); + }); } impl Vim { @@ -409,7 +416,8 @@ impl Object { | Object::Class | Object::EntireFile | Object::Comment - | Object::IndentObj { .. } => true, + | Object::IndentObj { .. } + | Object::SyntaxNode => true, } } @@ -437,7 +445,8 @@ impl Object { | Object::Comment | Object::EntireFile | Object::CurlyBrackets - | Object::AngleBrackets => true, + | Object::AngleBrackets + | Object::SyntaxNode => true, } } @@ -467,7 +476,8 @@ impl Object { | Object::Tag | Object::Comment | Object::Argument - | Object::IndentObj { .. } => Mode::Visual, + | Object::IndentObj { .. } + | Object::SyntaxNode => Mode::Visual, Object::Method | Object::Class => { if around { Mode::VisualLine @@ -683,6 +693,13 @@ impl Object { Object::Argument => argument(map, relative_to, around), Object::IndentObj { include_below } => indent(map, relative_to, around, include_below), Object::EntireFile => entire_file(map), + Object::SyntaxNode => { + if around { + larger_syntax_node(map, selection, times.unwrap_or(1)) + } else { + smaller_syntax_node(map, selection, times.unwrap_or(1)) + } + } } } @@ -1654,6 +1671,61 @@ fn surrounding_markers( ) } +fn smaller_syntax_node( + map: &DisplaySnapshot, + selection: Selection, + unwrap_or: usize, +) -> Option> { + todo!("take in editor and use ") +} + +fn larger_syntax_node( + map: &DisplaySnapshot, + selection: Selection, + count: usize, +) -> Option> { + let selection = selection.map(|p| { + map.display_point_to_anchor(p, Bias::Left) + .to_offset(&map.buffer_snapshot) + }); + let old_range = selection.start..selection.end; + let buffer = &map.buffer_snapshot; + + if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) { + // manually select word at selection + if ["string_content", "inline"].contains(&node.kind()) { + let (word_range, _) = buffer.surrounding_word(old_range.start, None); + // ignore if word is already selected + if !word_range.is_empty() && old_range != word_range { + let (last_word_range, _) = buffer.surrounding_word(old_range.end, None); + // only select word if start and end point belongs to same word + if word_range == last_word_range { + return Some( + word_range.start.to_display_point(map) + ..word_range.end.to_display_point(map), + ); + } + } + } + } + + let mut new_range = old_range.clone(); + while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone()) { + new_range = match containing_range { + multi_buffer::MultiOrSingleBufferOffsetRange::Single(_) => break, + multi_buffer::MultiOrSingleBufferOffsetRange::Multi(range) => range, + }; + if !node.is_named() { + continue; + } + if !map.intersects_fold(new_range.start) && !map.intersects_fold(new_range.end) { + break; + } + } + + return Some(new_range.start.to_display_point(map)..new_range.end.to_display_point(map)); +} + #[cfg(test)] mod test { use gpui::KeyBinding;