use mylsp::Position;
use tree_sitter::{Node, Point, TreeCursor};
pub struct TomlCursor<'a> {
text: &'a str,
cursor: TreeCursor<'a>,
}
impl TomlCursor<'_> {
pub fn visit_all<F: FnMut(Node)>(&mut self, mut f: F) {
let c = &mut self.cursor;
let mut visited = vec![];
loop {
let n = c.node();
if !visited.contains(&n.id()) {
visited.push(n.id());
f(n);
if c.goto_first_child() {
continue;
}
}
if !c.goto_next_sibling() && !c.goto_parent() {
break;
}
}
}
pub fn base_keys(&mut self, pos: &Position) -> Vec<String> {
let mut res = vec![];
if !self.walk_to(pos) {
return res;
}
if !self.parent_table() {
return res;
}
res.push(self.text().to_string());
self.cursor.goto_parent();
res
}
fn parent_table(&mut self) -> bool {
loop {
if self.is_table() {
break;
}
let id = self.cursor.node().id();
if !self.cursor.goto_parent() {
return false;
}
if !self.cursor.goto_first_child() {
return false;
}
if self.is_table() {
break;
}
if self.cursor.node().id() == id {
if !self.cursor.goto_parent() {
return false;
}
continue;
}
if !self.cursor.goto_parent() {
return false;
}
}
if !self.cursor.goto_first_child() {
return false;
}
while self.cursor.goto_next_sibling() && !self.is_key() {}
self.is_key()
}
fn walk_to(&mut self, pos: &Position) -> bool {
let c = &mut self.cursor;
let p = Point {
row: pos.line,
column: pos.character,
};
let mut id = None;
while let Some(i) = c.goto_first_child_for_point(p) {
id = Some(i);
}
id.is_some()
}
fn text(&self) -> &str {
let n = self.cursor.node();
&self.text[n.start_byte()..n.end_byte()]
}
fn is_key(&mut self) -> bool {
let n = self.cursor.node();
n.kind() == "bare_key" || n.kind() == "quoted_key"
}
fn is_table(&mut self) -> bool {
let n = self.cursor.node();
n.kind() == "table"
}
}
impl<'a> From<(&'a str, TreeCursor<'a>)> for TomlCursor<'a> {
fn from(value: (&'a str, TreeCursor<'a>)) -> Self {
Self {
text: value.0,
cursor: value.1,
}
}
}
#[cfg(test)]
mod test {
use tree_sitter::{Parser, Tree};
use crate::toml::TomlCursor;
const EMPTYFILE: &str = "\n";
#[test]
fn test_base_no_table() {
let text = EMPTYFILE;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(0, 0).into());
assert!(keys.is_empty());
}
const PACKAGEFILE: &str = "[package]\n";
#[test]
fn test_base_keys_line_table() {
let text = PACKAGEFILE;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(0, 0).into());
assert_eq!(keys, &["package"]);
}
#[test]
fn test_base_keys_inside_table_empty() {
let text = PACKAGEFILE;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(1, 0).into());
assert_eq!(keys, &["package"]);
}
const INCOMPLETENAME: &str = "[package]\nna";
#[test]
fn test_base_keys_inside_table_prop() {
let text = INCOMPLETENAME;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(1, 2).into());
assert_eq!(keys, &["package"]);
}
const DOUBLETABLE: &str = "[package]\n\n[dependencies]\n";
#[test]
fn test_double_table_empty() {
let text = DOUBLETABLE;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(3, 0).into());
assert_eq!(keys, &["dependencies"]);
}
const DOUBLETABLEPROP: &str = "[package]\n\n[dependencies]\na='b'\n";
#[test]
fn test_double_table_prop() {
let text = DOUBLETABLEPROP;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.base_keys(&(4, 0).into());
assert_eq!(keys, &["dependencies"]);
}
fn tree_of(text: &str) -> Tree {
let mut parser = Parser::new();
parser
.set_language(tree_sitter_toml::language())
.expect("Error loading toml grammar");
parser
.parse(text, None)
.expect("Error parsing with tree-sitter")
}
}