From e9546cb0e8709fe270ddef2525b6e63520a7799c Mon Sep 17 00:00:00 2001 From: xenia Date: Tue, 19 Dec 2023 18:26:43 +0100 Subject: [PATCH] Add tree-quickjump --- kakrc.sample | 7 +++- rc/tree.kak | 39 ++++++++++++++++++ src/kakoune.rs | 10 +++-- src/main.rs | 109 +++++++++++++++++++++++++++++++++++++++++++++++-- src/tree.rs | 1 + 5 files changed, 157 insertions(+), 9 deletions(-) diff --git a/kakrc.sample b/kakrc.sample index 268c8f5..8d59e29 100644 --- a/kakrc.sample +++ b/kakrc.sample @@ -19,9 +19,11 @@ map -docstring "disable highlighting" global tree '&' ":tree-always-hig declare-user-mode tree-children declare-user-mode tree-next declare-user-mode tree-prev +declare-user-mode tree-quickjump -map global normal 'f' ":enter-user-mode tree-next" -map global normal 'F' ":enter-user-mode tree-children" +map global normal 'f' ":enter-user-mode tree-quickjump" +map global normal 'F' ":enter-user-mode tree-next" +map global normal '' ":enter-user-mode tree-children" evaluate-commands %sh{ echo "i,identifier @@ -37,6 +39,7 @@ while IFS=, read -r cmd group ; do echo "map -docstring '$group child' global tree-children '$cmd' ':tree-select-children $group'" echo "map -docstring 'next $group' global tree-next '$cmd' ':tree-select-next-node $group'" echo "map -docstring 'previous $group' global tree-prev '$cmd' ':tree-select-previous-node $group'" + echo "map -docstring 'quickjump to $group' global tree-quickjump '$cmd' ':tree-quickjump $group'" done } diff --git a/rc/tree.kak b/rc/tree.kak index 348aa8c..30bd897 100644 --- a/rc/tree.kak +++ b/rc/tree.kak @@ -16,6 +16,15 @@ declare-option str tree_highlight_style "black,blue" declare-option -hidden range-specs tree_highlight add-highlighter global/tree_highlight ranges tree_highlight +declare-option str tree_quickjump_highlight_style "black,green" +declare-option -hidden range-specs tree_quickjump_replace +add-highlighter global/tree_quickjump_replace replace-ranges tree_quickjump_replace + +declare-option -hidden range-specs tree_quickjump_highlight +add-highlighter global/tree_quickjump_highlight ranges tree_quickjump_highlight + +declare-option -hidden range-specs tree_quickjump + define-command -hidden tree-command -params 1..2 -docstring %{ tree-command [] Send request to kak-tree and evaluate response. @@ -89,6 +98,36 @@ define-command tree-select-first-child -params ..1 -docstring %{ execute-keys , } +define-command tree-quickjump -params 1 -docstring %{ + tree-quickjump [] + Opens a menu to jump to a node of the specified kind +} %{ + tree-command-with-optional-kind QuickJump %arg{1} + + # We have now set the tree_quickjump variable to contain each location to jump to + prompt -menu \ + -shell-script-candidates 'IFS=" "; for entry in $kak_opt_tree_quickjump ; do echo "${entry#*|}" ; done' "Jump to " \ + -on-abort %{ + set-option buffer tree_quickjump "%val{timestamp}" + set-option buffer tree_quickjump_highlight "%val{timestamp}" + set-option buffer tree_quickjump_replace "%val{timestamp}" + } \ + %{evaluate-commands %sh{ + # loop through tree_quickjump to find the corresponding selection + IFS=" " + for entry in $kak_opt_tree_quickjump ; do + echo "echo -debug \"testing $entry ($kak_text)\"" + [ "$entry" = "${entry%|$kak_text}" ] && continue + echo "echo -debug \"yes\"" + echo "select ${entry%|$kak_text}" + done + echo 'set-option buffer tree_quickjump "%val{timestamp}"' + echo 'set-option buffer tree_quickjump_highlight "%val{timestamp}"' + echo 'set-option buffer tree_quickjump_replace "%val{timestamp}"' + } + } +} + define-command tree-node-highlight -docstring %{ tree-node-highlight Highlight the current node using the tree_highlight_style face diff --git a/src/kakoune.rs b/src/kakoune.rs index c305337..06e2e04 100644 --- a/src/kakoune.rs +++ b/src/kakoune.rs @@ -10,13 +10,14 @@ pub fn select_ranges(buffer: &[String], ranges: &[Range]) -> String { } pub fn highlight_ranges(buffer: &[String], ranges: &[Range]) -> String { - format!("set-option buffer tree_highlight %val{{timestamp}} {}", ranges_to_range_desc(&buffer, &ranges, "%opt{tree_highlight_style}".to_string())) + format!("set-option buffer tree_highlight %val{{timestamp}} {}", ranges_to_range_desc(&buffer, &ranges, |_i| "%opt{tree_highlight_style}".to_string())) } -pub fn ranges_to_range_desc(buffer: &[String], ranges: &[Range], string: String) -> String { +pub fn ranges_to_range_desc String>(buffer: &[String], ranges: &[Range], decorate: F) -> String { ranges .iter() - .map(|range| { + .enumerate() + .map(|(i, range)| { let mut end_row = range.end_point.row; let mut end_column = range.end_point.column; if end_column > 0 { @@ -29,7 +30,7 @@ pub fn ranges_to_range_desc(buffer: &[String], ranges: &[Range], string: String) "\"{},{}|{}\"", point_to_kak_coords(buffer, range.start_point), point_to_kak_coords(buffer, Point::new(end_row, end_column)), - string, + decorate(i), ) }) .join(" ") @@ -113,3 +114,4 @@ fn kak_coords_to_byte_and_point(buffer: &[String], coords: &str) -> (usize, Poin .unwrap(); (byte, Point::new(row, column)) } + diff --git a/src/main.rs b/src/main.rs index 8cf5015..f9ffdd8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ use clap::{crate_version, App, Arg}; use serde::Deserialize; use std::io::Read; use toml; -use tree_sitter::{Node, Parser, Range}; +use tree_sitter::{Node, Parser, Range, Point}; mod config; mod ffi; @@ -28,6 +28,7 @@ enum Op { SelectParentNode { kind: Option }, SelectPreviousNode { kind: Option }, SelectNode, + QuickJump { kind: Option }, } fn main() { @@ -132,7 +133,7 @@ fn handle_request(config: &Config, request: &Request) -> String { loop { let parent = cursor.parent(); loop { - if filetype_config.is_node_visible(cursor) && node_of_kinds(cursor, &kinds) { + if filetype_config.is_node_visible(cursor) && node_of_kinds(cursor, kinds.as_deref()) { if cursor != node { new_ranges.push(cursor.range()); continue 'outer_next; @@ -197,6 +198,60 @@ fn handle_request(config: &Config, request: &Request) -> String { } kakoune::highlight_ranges(&buffer, &new_ranges) } + Op::QuickJump { kind } => { + const LETTERS: &[char] = &[ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'å', 'ä', 'ö', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Å', 'Ä', 'Ö', + ]; + + let kind = kind.clone().unwrap(); + let main_range = &ranges[0]; + let main_node = tree::shrink_to_range(tree.root_node(), main_range); + let kinds = filetype_config.resolve_alias(&kind); + + let mut nodes = next_n_nodes_of_kind(main_node, &kinds, LETTERS.len() / 2, Direction::Forward); + nodes.extend(next_n_nodes_of_kind(main_node, &kinds, LETTERS.len() - nodes.len(), Direction::Backward)); + + for node in &nodes { + new_ranges.push(node.range()); + } + + // We don't want any ranges that overlap the first char + let mut longest_at_point: std::collections::HashMap = Default::default(); + for range in &new_ranges { + let range_len = range.end_byte - range.start_byte; + + longest_at_point.insert( + range.start_point, + longest_at_point.get(&range.start_point).copied().unwrap_or(0).max(range_len) + ); + } + + new_ranges.retain(|range| { + let range_len = range.end_byte - range.start_byte; + range_len >= longest_at_point.get(&range.start_point).copied().unwrap_or(0) + }); + + eprintln!("ranges = {:?}", new_ranges); + + let new_ranges_first_char: Vec = new_ranges + .iter() + .map(|range| Range { + start_byte: range.start_byte, end_byte: range.start_byte, + start_point: range.start_point, + end_point: Point { row: range.start_point.row, column: range.start_point.column + 1 }, + }) + .collect(); + + let replace_ranges = format!("set-option buffer tree_quickjump_replace %val{{timestamp}} {}", kakoune::ranges_to_range_desc(&buffer, &new_ranges_first_char, |i| LETTERS[i].to_string())); + + let highlight_ranges = format!("set-option buffer tree_quickjump_highlight %val{{timestamp}} {}", kakoune::ranges_to_range_desc(&buffer, &new_ranges_first_char, |_i| "%opt{tree_quickjump_highlight_style}".to_string())); + + let set_option = format!("set-option buffer tree_quickjump %val{{timestamp}} {}", kakoune::ranges_to_range_desc(&buffer, &new_ranges, |i| LETTERS[i].to_string())); + + format!("{replace_ranges} ; {highlight_ranges} ; {set_option}") + } } } @@ -242,7 +297,55 @@ fn find_parent_of_interest<'a>( } } -fn node_of_kinds(node: Node, kinds: &Option>) -> bool { +enum Direction { + Forward, Backward, +} + +fn next_n_nodes_of_kind<'a>(node: Node<'a>, kinds: &[String], n: usize, dir: Direction) -> Vec> { + let mut current = node; + + let mut visited = Vec::new(); + + 'outer: while visited.len() < n { + eprintln!("At node kind = {}, {current:?}", node.kind()); + if kinds.iter().any(|kind| kind == current.kind()) { + visited.push(current); + } + let first_child = match dir { + Direction::Forward => current.child(0), + Direction::Backward => current.child(current.child_count() - 1), + }; + if let Some(first_child) = first_child { + current = first_child; + } else { + loop { + match dir { + Direction::Forward => { + if let Some(sibling) = current.next_sibling() { + current = sibling; + break; + } + } + Direction::Backward => { + if let Some(sibling) = current.prev_sibling() { + current = sibling; + break; + } + } + } + if let Some(parent) = current.parent() { + current = parent; + } else { + break 'outer; + } + } + } + } + + visited +} + +fn node_of_kinds(node: Node, kinds: Option<&[String]>) -> bool { kinds .as_ref() .and_then(|kinds| Some(kinds.iter().any(|x| x == node.kind()))) diff --git a/src/tree.rs b/src/tree.rs index 7bcf0c1..de086d3 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -37,3 +37,4 @@ fn highest_node_of_same_range<'a>(current_node: Node<'a>) -> Node<'a> { } node } +