use std::ops::{Deref, DerefMut};
use tree_sitter::{Node, Parser, Point, Tree, TreeCursor};
use mylsp::prelude::*;
#[derive(Clone, Debug)]
pub struct Doc {
uri: String,
text: String,
tree: Tree,
diagnostics: Diagnostics,
}
impl Doc {
pub fn new(uri: &str, text: String) -> Self {
let mut parser = Parser::new();
parser
.set_language(tree_sitter_toml::language())
.expect("Error loading toml grammar");
let tree = parser
.parse(&text, None)
.expect("Error parsing with tree-sitter");
let diagnostics = make_diagnostics(&tree);
Self {
uri: uri.to_string(),
text,
tree,
diagnostics,
}
}
pub fn diagnostics(&self) -> &Diagnostics {
&self.diagnostics
}
pub fn completions_at(&self, pos: &Position) -> CompletionList {
let mut c = self.tree.walk();
let mut items = vec![];
let empty_result = CompletionList {
is_incomplete: false,
items: vec![],
};
if !walk_to_node_at_position(&mut c, pos) {
return empty_result;
}
if !walk_to_named_node_before(&mut c) {
return empty_result;
}
let block = &self.text[c.node().start_byte()..c.node().end_byte()];
if is_node_key(c.node()) {
items.push(CompletionItem {
label: block.to_string(),
kind: Some(CompletionItemKind::Keyword),
detail: None,
});
}
CompletionList {
is_incomplete: false,
items,
}
}
}
#[derive(Default, Clone, Debug)]
pub struct Diagnostics(Vec<Diagnostic>);
impl From<Vec<Diagnostic>> for Diagnostics {
fn from(value: Vec<Diagnostic>) -> Self {
Self(value)
}
}
impl Deref for Diagnostics {
type Target = Vec<Diagnostic>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Diagnostics {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<&Doc> for Notification {
fn from(value: &Doc) -> Self {
(
"textDocument/publishDiagnostics",
PublishDiagnosticsParams {
uri: value.uri.clone(),
version: None,
diagnostics: value.diagnostics().to_vec(),
},
)
.into()
}
}
impl From<&Doc> for DocumentDiagnosticReport {
fn from(value: &Doc) -> Self {
DocumentDiagnosticReport {
kind: "full".to_string(),
result_id: None,
items: value.diagnostics().to_vec(),
}
}
}
fn make_diagnostics(tree: &Tree) -> Diagnostics {
let mut res = vec![];
visit_all(tree, |n| {
if n.is_error() {
res.push(error_node_to_diagnostic(n));
}
});
res.into()
}
fn error_node_to_diagnostic(n: Node) -> Diagnostic {
Diagnostic {
range: node_to_range(&n),
message: n.to_sexp(),
severity: Some(DiagnosticSeverity::Error),
..Default::default()
}
}
fn node_to_range(n: &Node) -> Range {
let start = n.start_position();
let end = n.end_position();
Range {
start: Position {
line: start.row,
character: start.column,
},
end: Position {
line: end.row,
character: end.column,
},
}
}
fn visit_all<F: FnMut(Node)>(tree: &Tree, mut f: F) {
let mut c = tree.walk();
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;
}
}
}
fn walk_to_node_at_position(c: &mut TreeCursor, pos: &Position) -> bool {
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 walk_to_named_node_before(c: &mut TreeCursor) -> bool {
while !c.node().is_named() && c.goto_parent() {}
c.node().is_named()
}
fn is_node_key(n: Node) -> bool {
n.kind() == "dotted_key" || n.kind() == "bare_key" || n.kind() == "quoted_key"
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_empty() {
let doc = Doc::new("", String::new());
assert_no_errors(&doc);
}
#[test]
fn test_error() {
let doc = Doc::new("", "[a]b".to_string());
assert_errors(&doc, &[r(0, 0, 0, 4)]);
}
#[test]
fn test_error_nested() {
let doc = Doc::new(
"",
r#"
[a]
b={c="d"}
[a]b
"#
.to_string(),
);
assert_errors(&doc, &[r(3, 3, 4, 0)]);
}
#[test]
fn test_generic_completions_empty_doc() {
let doc = Doc::new("", String::new());
let comps = doc.completions_at(&Position {
line: 1,
character: 2,
});
assert!(comps.items.is_empty());
}
#[test]
fn test_completion_keys() {
let doc = Doc::new("", "key = \"value\"".to_string());
assert_completions(&doc, &(0, 2).into(), &[c("key", KW)]);
}
const KW: CompletionItemKind = CompletionItemKind::Keyword;
fn c(text: &str, kind: CompletionItemKind) -> CompletionItem {
CompletionItem {
label: text.to_string(),
kind: Some(kind),
detail: None,
}
}
fn assert_completions(doc: &Doc, pos: &Position, completions: &[CompletionItem]) {
let dcs_at = doc.completions_at(pos);
let mut dcs = dcs_at.items.iter();
for c in completions {
let dc = dcs
.next()
.expect("Assert failed, completions count not matching");
assert_eq!(dc, c);
}
assert!(dcs.next().is_none());
}
fn r(
start_line: usize,
start_character: usize,
end_line: usize,
end_character: usize,
) -> Range {
((start_line, start_character).into()..(end_line, end_character).into()).into()
}
fn assert_no_errors(doc: &Doc) {
assert!(doc.diagnostics().is_empty());
let root = doc.tree.root_node();
assert!(!root.has_error());
}
fn assert_errors(doc: &Doc, ranges: &[Range]) {
let mut dgs = doc.diagnostics().iter();
for range in ranges {
let dg = dgs
.next()
.expect("Assert failed, ranges and diagnostic count not matching");
assert_eq!(&dg.range, range);
}
assert!(dgs.next().is_none());
let root = doc.tree.root_node();
assert!(root.has_error());
}
}