use std::cmp::min;
use mylsp::Position;
use tree_sitter::{Node, 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 prefix(&mut self, pos: Position) -> String {
let mut res = vec![];
let mut base = "";
let mut partial = "";
for (i, l) in self.text.lines().enumerate() {
if i == pos.line {
partial = l[..min(pos.character + 1, l.len())].trim();
let idx = partial.find('=').unwrap_or(partial.len());
partial = partial[..min(partial.len(), idx)].trim();
break;
}
let l = l.trim();
if l.starts_with('[') && l.ends_with(']') {
base = l[1..l.len() - 1].trim();
}
}
if !base.is_empty() {
for item in base.split('.') {
let item = item.trim();
res.push(item.to_string());
}
}
let mut pk = partial.split('.').peekable();
while let Some(item) = pk.next()
&& pk.peek().is_some()
{
let item = item.trim();
res.push(item.to_string());
}
res.join(".")
}
}
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.prefix((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.prefix((0, 0).into());
assert!(keys.is_empty());
}
#[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.prefix((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.prefix((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.prefix((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.prefix((4, 0).into());
assert_eq!(keys, "dependencies");
}
const DOTTABLE: &str = "[lints.clippy]\n";
#[test]
fn test_dot_table() {
let text = DOTTABLE;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.prefix((1, 0).into());
assert_eq!(keys, "lints.clippy");
}
const DOTTABLEFIELD: &str = "[lints]\nclippy.";
#[test]
fn test_dot_table_field() {
let text = DOTTABLEFIELD;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.prefix((1, 6).into());
assert_eq!(keys, "lints.clippy");
}
const DOTFIELD: &str = "clippy.";
#[test]
fn test_dot_field() {
let text = DOTFIELD;
let tree = tree_of(text);
let mut tc: TomlCursor = (text, tree.walk()).into();
let keys = tc.prefix((0, 6).into());
assert_eq!(keys, "clippy");
}
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")
}
}