Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Cousin (tree-sitter navigation) support + sticky navigation mode. #321

Merged
merged 3 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion kak-tree-sitter/rc/static.kak
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ define-command kak-tree-sitter-req-object-text-objects -params 1 %{
# Send a single request to modify selections with tree-sitter navigation.
define-command kak-tree-sitter-req-nav -params 1 %{
evaluate-commands -no-hooks %{
echo -to-file %opt{kts_cmd_fifo_path} -- "{ ""type"": ""nav"", ""client"": ""%val{client}"", ""buffer"": ""%val{bufname}"", ""lang"": ""%opt{kts_lang}"", ""selections"": ""%val{selections_desc}"", ""dir"": ""%arg{1}"" }"
echo -to-file %opt{kts_cmd_fifo_path} -- "{ ""type"": ""nav"", ""client"": ""%val{client}"", ""buffer"": ""%val{bufname}"", ""lang"": ""%opt{kts_lang}"", ""selections"": ""%val{selections_desc}"", ""dir"": %arg{1} }"
write %opt{kts_buf_fifo_path}
}
}
Expand Down
103 changes: 71 additions & 32 deletions kak-tree-sitter/rc/text-objects.kak
Original file line number Diff line number Diff line change
Expand Up @@ -8,61 +8,100 @@ declare-user-mode tree-sitter-find-rev
declare-user-mode tree-sitter-find-extend
declare-user-mode tree-sitter-find-extend-rev

map global tree-sitter / ':enter-user-mode tree-sitter-search<ret>' -docstring 'search next'
map global tree-sitter <a-/> ':enter-user-mode tree-sitter-search-rev<ret>' -docstring 'search prev'
map global tree-sitter ? ':enter-user-mode tree-sitter-search-extend<ret>' -docstring 'search(extend) next'
map global tree-sitter <a-?> ':enter-user-mode tree-sitter-search-extend-rev<ret>' -docstring 'search(extend) prev'
map global tree-sitter f ':enter-user-mode tree-sitter-find<ret>' -docstring 'find next'
map global tree-sitter <a-f> ':enter-user-mode tree-sitter-find-rev<ret>' -docstring 'find prev'
map global tree-sitter F ':enter-user-mode tree-sitter-find-extend<ret>' -docstring 'find(extend) next'
map global tree-sitter <a-F> ':enter-user-mode tree-sitter-find-extend-rev<ret>' -docstring 'find(extend) prev'
map global tree-sitter s ':kak-tree-sitter-req-nav parent<ret>' -docstring 'select parent'
map global tree-sitter t ':kak-tree-sitter-req-nav first_child<ret>' -docstring 'select first child'
map global tree-sitter c ':kak-tree-sitter-req-nav prev_sibling<ret>' -docstring 'select previous sibling'
map global tree-sitter r ':kak-tree-sitter-req-nav next_sibling<ret>' -docstring 'select next sibling'
map global tree-sitter / ':enter-user-mode tree-sitter-search<ret>' -docstring 'search next'
map global tree-sitter <a-/> ':enter-user-mode tree-sitter-search-rev<ret>' -docstring 'search prev'
map global tree-sitter ? ':enter-user-mode tree-sitter-search-extend<ret>' -docstring 'search(extend) next'
map global tree-sitter <a-?> ':enter-user-mode tree-sitter-search-extend-rev<ret>' -docstring 'search(extend) prev'
map global tree-sitter f ':enter-user-mode tree-sitter-find<ret>' -docstring 'find next'
map global tree-sitter <a-f> ':enter-user-mode tree-sitter-find-rev<ret>' -docstring 'find prev'
map global tree-sitter F ':enter-user-mode tree-sitter-find-extend<ret>' -docstring 'find(extend) next'
map global tree-sitter <a-F> ':enter-user-mode tree-sitter-find-extend-rev<ret>' -docstring 'find(extend) prev'
map global tree-sitter s ":kak-tree-sitter-req-nav '""parent""'<ret>" -docstring 'select parent'
map global tree-sitter t ":kak-tree-sitter-req-nav '""first_child""'<ret>" -docstring 'select first child'
map global tree-sitter c ":kak-tree-sitter-req-nav '{ ""prev_sibling"": { ""cousin"": false } }'<ret>" -docstring 'select previous sibling'
map global tree-sitter r ":kak-tree-sitter-req-nav '{ ""next_sibling"": { ""cousin"": false } }'<ret>" -docstring 'select next sibling'
map global tree-sitter C ":kak-tree-sitter-req-nav '{ ""prev_sibling"": { ""cousin"": true } }'<ret>" -docstring 'select previous sibling (cousin)'
map global tree-sitter R ":kak-tree-sitter-req-nav '{ ""next_sibling"": { ""cousin"": true } }'<ret>" -docstring 'select next sibling (cousin)'
map global tree-sitter T ':enter-user-mode tree-sitter-nav-sticky<ret>' -docstring 'sticky tree navigation'

map global tree-sitter-search f ':kak-tree-sitter-req-text-objects function.around search_next<ret>' -docstring 'function'
map global tree-sitter-search a ':kak-tree-sitter-req-text-objects parameter.around search_next<ret>' -docstring 'parameter'
map global tree-sitter-search t ':kak-tree-sitter-req-text-objects class.around search_next<ret>' -docstring 'class'
map global tree-sitter-search c ':kak-tree-sitter-req-text-objects comment.around search_next<ret>' -docstring 'comment'
map global tree-sitter-search T ':kak-tree-sitter-req-text-objects test.around search_next<ret>' -docstring 'test'
map global tree-sitter-search t ':kak-tree-sitter-req-text-objects class.around search_next<ret>' -docstring 'class'
map global tree-sitter-search c ':kak-tree-sitter-req-text-objects comment.around search_next<ret>' -docstring 'comment'
map global tree-sitter-search T ':kak-tree-sitter-req-text-objects test.around search_next<ret>' -docstring 'test'

map global tree-sitter-search-rev f ':kak-tree-sitter-req-text-objects function.around search_prev<ret>' -docstring 'function'
map global tree-sitter-search-rev a ':kak-tree-sitter-req-text-objects parameter.around search_prev<ret>' -docstring 'parameter'
map global tree-sitter-search-rev t ':kak-tree-sitter-req-text-objects class.around search_prev<ret>' -docstring 'class'
map global tree-sitter-search-rev T ':kak-tree-sitter-req-text-objects test.around search_prev<ret>' -docstring 'test'
map global tree-sitter-search-rev t ':kak-tree-sitter-req-text-objects class.around search_prev<ret>' -docstring 'class'
map global tree-sitter-search-rev T ':kak-tree-sitter-req-text-objects test.around search_prev<ret>' -docstring 'test'

map global tree-sitter-search-extend f ':kak-tree-sitter-req-text-objects function.around search_extend_next<ret>' -docstring 'function'
map global tree-sitter-search-extend a ':kak-tree-sitter-req-text-objects parameter.around search_extend_next<ret>' -docstring 'parameter'
map global tree-sitter-search-extend t ':kak-tree-sitter-req-text-objects class.around search_extend_next<ret>' -docstring 'class'
map global tree-sitter-search-extend T ':kak-tree-sitter-req-text-objects test.around search_extend_next<ret>' -docstring 'test'
map global tree-sitter-search-extend t ':kak-tree-sitter-req-text-objects class.around search_extend_next<ret>' -docstring 'class'
map global tree-sitter-search-extend T ':kak-tree-sitter-req-text-objects test.around search_extend_next<ret>' -docstring 'test'

map global tree-sitter-search-extend-rev f ':kak-tree-sitter-req-text-objects function.around search_extend_prev<ret>' -docstring 'function'
map global tree-sitter-search-extend-rev a ':kak-tree-sitter-req-text-objects parameter.around search_extend_prev<ret>' -docstring 'parameter'
map global tree-sitter-search-extend-rev t ':kak-tree-sitter-req-text-objects class.around search_extend_prev<ret>' -docstring 'class'
map global tree-sitter-search-extend-rev T ':kak-tree-sitter-req-text-objects test.around search_extend_prev<ret>' -docstring 'test'
map global tree-sitter-search-extend-rev t ':kak-tree-sitter-req-text-objects class.around search_extend_prev<ret>' -docstring 'class'
map global tree-sitter-search-extend-rev T ':kak-tree-sitter-req-text-objects test.around search_extend_prev<ret>' -docstring 'test'

map global tree-sitter-find f ':kak-tree-sitter-req-text-objects function.around find_next<ret>' -docstring 'function'
map global tree-sitter-find a ':kak-tree-sitter-req-text-objects parameter.around find_next<ret>' -docstring 'parameter'
map global tree-sitter-find t ':kak-tree-sitter-req-text-objects class.around find_next<ret>' -docstring 'class'
map global tree-sitter-find T ':kak-tree-sitter-req-text-objects test.around find_next<ret>' -docstring 'test'
map global tree-sitter-find t ':kak-tree-sitter-req-text-objects class.around find_next<ret>' -docstring 'class'
map global tree-sitter-find T ':kak-tree-sitter-req-text-objects test.around find_next<ret>' -docstring 'test'

map global tree-sitter-find-rev f ':kak-tree-sitter-req-text-objects function.around find_prev<ret>' -docstring 'function'
map global tree-sitter-find-rev a ':kak-tree-sitter-req-text-objects parameter.around find_prev<ret>' -docstring 'parameter'
map global tree-sitter-find-rev t ':kak-tree-sitter-req-text-objects class.around find_prev<ret>' -docstring 'class'
map global tree-sitter-find-rev T ':kak-tree-sitter-req-text-objects test.around find_prev<ret>' -docstring 'test'
map global tree-sitter-find-rev t ':kak-tree-sitter-req-text-objects class.around find_prev<ret>' -docstring 'class'
map global tree-sitter-find-rev T ':kak-tree-sitter-req-text-objects test.around find_prev<ret>' -docstring 'test'

map global tree-sitter-find-extend f ':kak-tree-sitter-req-text-objects function.around extend_next<ret>' -docstring 'function'
map global tree-sitter-find-extend a ':kak-tree-sitter-req-text-objects parameter.around extend_next<ret>' -docstring 'parameter'
map global tree-sitter-find-extend t ':kak-tree-sitter-req-text-objects class.around extend_next<ret>' -docstring 'class'
map global tree-sitter-find-extend T ':kak-tree-sitter-req-text-objects test.around extend_next<ret>' -docstring 'test'
map global tree-sitter-find-extend t ':kak-tree-sitter-req-text-objects class.around extend_next<ret>' -docstring 'class'
map global tree-sitter-find-extend T ':kak-tree-sitter-req-text-objects test.around extend_next<ret>' -docstring 'test'

map global tree-sitter-find-extend-rev f ':kak-tree-sitter-req-text-objects function.around extend_prev<ret>' -docstring 'function'
map global tree-sitter-find-extend-rev a ':kak-tree-sitter-req-text-objects parameter.around extend_prev<ret>' -docstring 'parameter'
map global tree-sitter-find-extend-rev t ':kak-tree-sitter-req-text-objects class.around extend_prev<ret>' -docstring 'class'
map global tree-sitter-find-extend-rev T ':kak-tree-sitter-req-text-objects test.around extend_prev<ret>' -docstring 'test'
map global tree-sitter-find-extend-rev t ':kak-tree-sitter-req-text-objects class.around extend_prev<ret>' -docstring 'class'
map global tree-sitter-find-extend-rev T ':kak-tree-sitter-req-text-objects test.around extend_prev<ret>' -docstring 'test'

map global object f '<a-;>kak-tree-sitter-req-object-text-objects function<ret>' -docstring 'function (tree-sitter)'
map global object t '<a-;>kak-tree-sitter-req-object-text-objects class<ret>' -docstring 'type (tree-sitter)'
map global object f '<a-;>kak-tree-sitter-req-object-text-objects function<ret>' -docstring 'function (tree-sitter)'
map global object t '<a-;>kak-tree-sitter-req-object-text-objects class<ret>' -docstring 'type (tree-sitter)'
map global object a '<a-;>kak-tree-sitter-req-object-text-objects parameter<ret>' -docstring 'argument (tree-sitter)'
map global object T '<a-;>kak-tree-sitter-req-object-text-objects test<ret>' -docstring 'test (tree-sitter)'
map global object T '<a-;>kak-tree-sitter-req-object-text-objects test<ret>' -docstring 'test (tree-sitter)'

# sticky mode for navigation
declare-user-mode tree-sitter-nav-sticky

define-command -hidden kak-tree-sitter-nav-sticky-undo %{
execute-keys "<a-u>"
enter-user-mode tree-sitter-nav-sticky
}

define-command -hidden kak-tree-sitter-nav-sticky-parent %{
kak-tree-sitter-req-nav '"parent"'
enter-user-mode tree-sitter-nav-sticky
}

define-command -hidden kak-tree-sitter-nav-sticky-1st-child %{
kak-tree-sitter-req-nav '"first_child"'
enter-user-mode tree-sitter-nav-sticky
}

define-command -hidden kak-tree-sitter-nav-sticky-prev-sibling -params 1 %{
kak-tree-sitter-req-nav "{ ""prev_sibling"": { ""cousin"": %arg{1} }}"
enter-user-mode tree-sitter-nav-sticky
}

define-command -hidden kak-tree-sitter-nav-sticky-next-sibling -params 1 %{
kak-tree-sitter-req-nav "{ ""next_sibling"": { ""cousin"": %arg{1} }}"
enter-user-mode tree-sitter-nav-sticky
}

map global tree-sitter-nav-sticky s ':kak-tree-sitter-nav-sticky-parent<ret>' -docstring 'select parent'
map global tree-sitter-nav-sticky t ':kak-tree-sitter-nav-sticky-1st-child<ret>' -docstring 'select first child'
map global tree-sitter-nav-sticky C ':kak-tree-sitter-nav-sticky-prev-sibling true<ret>' -docstring 'select previous sibling (cousin)'
map global tree-sitter-nav-sticky R ':kak-tree-sitter-nav-sticky-next-sibling true<ret>' -docstring 'select next sibling (cousin)'
map global tree-sitter-nav-sticky c ':kak-tree-sitter-nav-sticky-prev-sibling false<ret>' -docstring 'select previous sibling'
map global tree-sitter-nav-sticky r ':kak-tree-sitter-nav-sticky-next-sibling false<ret>' -docstring 'select next sibling'
map global tree-sitter-nav-sticky u ':kak-tree-sitter-nav-sticky-undo<ret>' -docstring 'undo selection'
11 changes: 9 additions & 2 deletions kak-tree-sitter/src/nav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@ pub enum Dir {
// /// Last sibling of the current node if any.
// LastSibling,
/// Previous sibiling of the current node, if any.
PrevSibling,
PrevSibling {
/// Should we take cousins into account?
#[serde(default)]
cousin: bool,
},

/// Next sibling of the current node, if any.
NextSibling,
NextSibling {
/// Should we take cousins into account?
cousin: bool,
},
}
22 changes: 17 additions & 5 deletions kak-tree-sitter/src/selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
use tree_sitter::{Node, Point};

/// A single position in a buffer.
///
/// Kakoune position _1-based_, while tree-sitter selections are _0-based_.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct Pos {
pub line: usize,
Expand Down Expand Up @@ -41,10 +43,12 @@ impl Pos {
}
}

/// A single selection, containing an anchor and a cursor.
/// A single Kakoune selection, containing an anchor and a cursor.
///
/// Note: there is no rule about anchors and cursors. One can come before the other; do not assume anything about their
/// position.
///
/// Kakoune selections are always inclusive, while tree-sitter ranges are exclusive on their end boundary.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Sel {
pub anchor: Pos,
Expand Down Expand Up @@ -103,13 +107,21 @@ impl Sel {
/// Same as [`Sel::replace`], but with a node’s content.
pub fn replace_with_node(&self, node: &Node) -> Self {
let mut b: Pos = node.end_position().into();
b.col -= 1;
b.col -= 1; // kakoune selections are inclusive
self.replace(&node.start_position().into(), &b)
}

/// Check whether the cursor and anchor are at the same position.
pub fn is_fused(&self) -> bool {
self.anchor == self.cursor
/// Check whether a selection fully selects a node.
pub fn fully_selects(&self, node: &Node) -> bool {
let start: Pos = node.start_position().into();
let mut end: Pos = node.end_position().into();
end.col -= 1;

if self.anchor <= self.cursor {
self.anchor == start && self.cursor == end
} else {
self.cursor == start && self.anchor == end
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion kak-tree-sitter/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ impl FifoHandler {
buffer: buffer.clone(),
lang: lang.clone(),
selections,
dir: dir.clone(),
dir: *dir,
};

Ok(None)
Expand Down
39 changes: 35 additions & 4 deletions kak-tree-sitter/src/tree_sitter_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,9 @@ impl TreeState {
self
.find_sel_node(sel)
.and_then(|node| {
// if we are fused, we select the nearest node
if sel.is_fused() {
// if our selection is not the same as the node, we pick the node
if !sel.fully_selects(&node) {
log::debug!("selection {sel:?} doesn’t fully select node {node:?}");
return Some(node);
}

Expand All @@ -401,8 +402,10 @@ impl TreeState {
Dir::Parent => node.parent(),
Dir::FirstChild => node.child(0),
Dir::FirstSibling => node.parent().and_then(|node| node.child(0)),
Dir::PrevSibling => node.prev_sibling(),
Dir::NextSibling => node.next_sibling(),
Dir::PrevSibling { cousin } if cousin => Self::find_prev_sibling_or_cousin(&node),
Dir::NextSibling { cousin } if cousin => Self::find_next_sibling_or_cousin(&node),
Dir::PrevSibling { .. } => node.prev_sibling(),
Dir::NextSibling { .. } => node.next_sibling(),
};

log::debug!("navigated to node: {res:?}");
Expand All @@ -428,4 +431,32 @@ impl TreeState {

node
}

/// Get the next sibiling or cousin.
fn find_next_sibling_or_cousin<'a>(node: &Node<'a>) -> Option<Node<'a>> {
node.next_sibling().or_else(|| {
let parent = node.parent()?;
let parent_sibling = parent.next_sibling()?;

if parent_sibling.child_count() > 0 {
parent_sibling.child(0)
} else {
None
}
})
}

/// Get the previous sibiling or cousin.
fn find_prev_sibling_or_cousin<'a>(node: &Node<'a>) -> Option<Node<'a>> {
node.prev_sibling().or_else(|| {
let parent = node.parent()?;
let parent_sibling = parent.prev_sibling()?;

if parent_sibling.child_count() > 0 {
parent_sibling.child(parent_sibling.child_count() - 1)
} else {
None
}
})
}
}
Loading