kak-tree/src/main.rs

354 lines
13 KiB
Rust

use crate::config::{Config, FiletypeConfig};
use clap::{crate_version, App, Arg};
use serde::Deserialize;
use std::io::Read;
use toml;
use tree_sitter::{Node, Parser, Range, Point};
mod config;
mod ffi;
mod kakoune;
mod tree;
#[derive(Deserialize, Debug)]
struct Request {
op: Op,
filetype: String,
selections_desc: String,
content: String,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "type")]
enum Op {
NodeSExp,
HighlightCurrentNode,
SelectChildren { kind: Option<String> },
SelectNextNode { kind: Option<String> },
SelectParentNode { kind: Option<String> },
SelectPreviousNode { kind: Option<String> },
SelectNode,
QuickJump { kind: Option<String> },
}
fn main() {
let matches = cli();
if let Some(filetype) = matches.value_of("do-you-understand") {
let language = ffi::filetype_to_language(filetype);
if let Some(language) = language {
eprintln!("Known language. Trying to load");
let mut parser = Parser::new();
parser.set_language(language).unwrap();
eprintln!("Loaded successfully");
std::process::exit(0);
} else {
eprintln!("Unknown language");
std::process::exit(1);
}
}
let config = if let Some(config_path) = matches.value_of("config") {
Config::load(config_path).unwrap()
} else {
Config::default()
};
let mut request = String::new();
std::io::stdin().read_to_string(&mut request).unwrap();
let request: Request = toml::from_str(&request).unwrap();
let response = handle_request(&config, &request);
eprintln!("(request: {:?})", request);
println!("{}", response);
}
fn cli() -> clap::ArgMatches<'static> {
App::new("kak-tree")
.version(crate_version!())
.author("Ruslan Prokopchuk <fer.obbee@gmail.com>")
.about("Structural selections for Kakoune")
.arg(
Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Read config from FILE")
.takes_value(true),
)
.arg(
Arg::with_name("do-you-understand")
.long("do-you-understand")
.value_name("FILETYPE")
.help("Exit with 0 if FILETYPE is supported, non-zero otherwise")
.takes_value(true),
)
.arg(
Arg::with_name("v")
.short("v")
.multiple(true)
.help("Sets the level of verbosity"),
)
.get_matches()
}
fn handle_request(config: &Config, request: &Request) -> String {
let mut parser = Parser::new();
let language = ffi::filetype_to_language(&request.filetype).unwrap();
parser.set_language(language).unwrap();
let tree = parser.parse(&request.content, None).unwrap();
let buffer = request
.content
.split('\n')
.map(|s| format!("{}\n", s))
.collect::<Vec<_>>();
let ranges = kakoune::selections_desc_to_ranges(&buffer, &request.selections_desc);
let mut new_ranges = Vec::new();
let filetype_config = config.get_filetype_config(&request.filetype);
match &request.op {
Op::SelectNode => {
for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
new_ranges.push(node.range());
}
kakoune::select_ranges(&buffer, &new_ranges)
}
Op::SelectParentNode { kind } => {
let kinds = kind
.as_ref()
.and_then(|kind| Some(filetype_config.resolve_alias(kind)));
for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
let node = find_parent_of_interest(filetype_config, node, &kinds);
new_ranges.push(node.range());
}
kakoune::select_ranges(&buffer, &new_ranges)
}
Op::SelectNextNode { kind } | Op::SelectPreviousNode { kind } => {
let kinds = kind
.as_ref()
.and_then(|kind| Some(filetype_config.resolve_alias(kind)));
'outer_next: for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
let mut cursor = node;
loop {
let parent = cursor.parent();
loop {
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;
}
}
if let Op::SelectNextNode { .. } = request.op {
if let Some(next) = cursor.next_named_sibling() {
cursor = next;
} else {
break;
}
} else {
if let Some(prev) = cursor.prev_named_sibling() {
cursor = prev;
} else {
break;
}
}
}
if let Some(parent) = parent {
cursor = parent;
} else {
break;
}
}
// TODO: Do we want this behaviour?
// let node = find_parent_of_interest(filetype_config, node, &kinds);
// new_ranges.push(node.range());
}
kakoune::select_ranges(&buffer, &new_ranges)
}
Op::SelectChildren { kind } => {
match kind {
Some(kind) => {
let kinds = filetype_config.resolve_alias(kind);
for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
select_nodes(&node, &kinds, &mut new_ranges);
}
}
None => {
for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
for child in tree::named_children(&node) {
if filetype_config.is_node_visible(child) {
new_ranges.push(child.range());
}
}
}
}
}
kakoune::select_ranges(&buffer, &new_ranges)
}
Op::NodeSExp => {
let node = tree::shrink_to_range(tree.root_node(), &ranges[0]);
format!("info '{}'", node.to_sexp())
}
Op::HighlightCurrentNode => {
for range in &ranges {
let node = tree::shrink_to_range(tree.root_node(), range);
new_ranges.push(node.range());
}
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<Point, usize> = 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<Range> = 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}")
}
}
}
fn select_nodes(node: &Node, kinds: &[String], new_ranges: &mut Vec<Range>) {
for child in tree::named_children(&node) {
if kinds.iter().any(|kind| kind == child.kind()) {
new_ranges.push(child.range());
} else {
select_nodes(&child, kinds, new_ranges);
}
}
}
fn traverse_up_to_node_which_matters<'a>(
filetype_config: &FiletypeConfig,
current_node: Node<'a>,
) -> Node<'a> {
let mut opt_node = Some(current_node);
while let Some(node) = opt_node.filter(|&n| !(n.is_named() && filetype_config.is_node_visible(n))) {
opt_node = node.parent();
}
opt_node.unwrap_or(current_node)
}
fn find_parent_of_interest<'a>(
filetype_config: &FiletypeConfig,
current_node: Node<'a>,
kinds: &Option<Vec<String>>,
) -> Node<'a> {
let parent = current_node.parent();
match &kinds {
Some(kinds) => {
let mut cursor = parent;
while let Some(node) = cursor {
if kinds.iter().any(|kind| kind == node.kind()) {
return node;
}
cursor = node.parent();
}
current_node
}
None => traverse_up_to_node_which_matters(filetype_config, parent.unwrap_or(current_node)),
}
}
enum Direction {
Forward, Backward,
}
fn next_n_nodes_of_kind<'a>(node: Node<'a>, kinds: &[String], n: usize, dir: Direction) -> Vec<Node<'a>> {
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())))
.unwrap_or(true)
}