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 }, SelectNextNode { kind: Option }, SelectParentNode { kind: Option }, SelectPreviousNode { kind: Option }, SelectNode, QuickJump { kind: Option }, } 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 ") .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::>(); 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 = 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}") } } } fn select_nodes(node: &Node, kinds: &[String], new_ranges: &mut Vec) { 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>, ) -> 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> { 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) }