use std::{cmp::min, iter::Enumerate, str::Lines};
use mylsp::prelude::*;
pub fn prefix(text: &str, pos: Position) -> String {
let mut res = vec![];
let mut base = "";
let mut branched_key = None;
let mut partial = "";
let mut found_equals = false;
for (i, l) in text.lines().enumerate() {
if i == pos.line {
partial = l[..min(pos.character + 1, l.len())].trim();
branched_key = branched_key_on_equals(partial);
let find = partial.find('=');
let idx = find.unwrap_or(partial.len());
found_equals = find.is_some();
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()
&& (found_equals || pk.peek().is_some())
{
let item = item.trim();
res.push(item.to_string());
}
if let Some(branched_key) = branched_key {
res.push(branched_key);
}
res.join(".")
}
fn branched_key_on_equals(partial: &str) -> Option<String> {
if let Some(first) = partial.split('=').next() {
let mut split = partial.split('=');
split.next_back();
if let Some(last) = split.next_back()
&& last.trim() != first.trim()
&& let Some(lt) = last.trim().split(' ').next_back()
{
return Some(lt.trim().to_string());
}
}
None
}
pub fn extract_all(text: &str) -> Vec<(String, String)> {
let mut prefix = "";
let mut res = vec![];
let mut iter = text.lines().enumerate();
while let Some((_, l)) = iter.next() {
if l.trim().starts_with('[') && l.trim().ends_with(']') {
prefix = l
.trim()
.strip_prefix('[')
.unwrap_or(l)
.strip_suffix(']')
.unwrap_or(l);
continue;
}
let Some(idx) = l.find('=') else {
continue;
};
let left = &l[..idx].trim();
let mut key = String::new();
if !prefix.is_empty() {
key += prefix;
key += ".";
}
key += left;
let rhs = l[min(idx + 1, l.len())..].trim();
let value = if rhs.starts_with('[') && !rhs.ends_with(']') {
format!("{}{}", rhs, complete_until(&mut iter, '[', ']'))
} else if rhs.starts_with('{') && !rhs.ends_with('}') {
format!("{}{}", rhs, complete_until(&mut iter, '{', '}'))
} else {
rhs.to_string()
};
res.push((key, value));
}
res
}
fn complete_until(iter: &mut Enumerate<Lines<'_>>, _starting: char, ending: char) -> String {
let mut rest = String::new();
for (_, l) in iter {
rest += l;
if l.trim().ends_with(ending) {
break;
}
}
rest
}
#[cfg(test)]
mod test {
use super::*;
const CURLY_VALUE: &str = "key = { a = 1 }";
#[test]
fn test_prefix_inside_curly() {
let text = CURLY_VALUE;
let keys = prefix(text, (0, 8).into());
assert_eq!(keys, "key");
}
const CURLY_VALUE_TABLE: &str = "[tab]\nkey = { a = 1 }";
#[test]
fn test_prefix_inside_curly_table() {
let text = CURLY_VALUE_TABLE;
let keys = prefix(text, (1, 8).into());
assert_eq!(keys, "tab.key");
}
const EMPTYFILE: &str = "\n";
#[test]
fn test_base_no_table() {
let text = EMPTYFILE;
let keys = prefix(text, (0, 0).into());
assert!(keys.is_empty());
}
const PACKAGEFILE: &str = "[package]\n";
#[test]
fn test_base_keys_line_table() {
let text = PACKAGEFILE;
let keys = prefix(text, (0, 0).into());
assert!(keys.is_empty());
}
#[test]
fn test_base_keys_inside_table_empty() {
let text = PACKAGEFILE;
let keys = prefix(text, (1, 0).into());
assert_eq!(keys, "package");
}
const INCOMPLETENAME: &str = "[package]\nna";
#[test]
fn test_base_keys_inside_table_prop() {
let text = INCOMPLETENAME;
let keys = prefix(text, (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 keys = prefix(text, (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 keys = prefix(text, (4, 0).into());
assert_eq!(keys, "dependencies");
}
const DOTTABLE: &str = "[lints.clippy]\n";
#[test]
fn test_dot_table() {
let text = DOTTABLE;
let keys = prefix(text, (1, 0).into());
assert_eq!(keys, "lints.clippy");
}
const DOTTABLEFIELD: &str = "[lints]\nclippy.";
#[test]
fn test_dot_table_field() {
let text = DOTTABLEFIELD;
let keys = prefix(text, (1, 6).into());
assert_eq!(keys, "lints.clippy");
}
const DOTFIELD: &str = "clippy.";
#[test]
fn test_dot_field() {
let text = DOTFIELD;
let keys = prefix(text, (0, 6).into());
assert_eq!(keys, "clippy");
}
const NOKEY: &str = "";
#[test]
fn test_no_key() {
let text = NOKEY;
let value = extract_all(text);
assert!(value.is_empty());
}
const NOVALUE: &str = "key";
#[test]
fn test_no_value() {
let text = NOVALUE;
let value = extract_all(text);
assert!(value.is_empty());
}
const KEYONLY: &str = "key=";
#[test]
fn test_key_empty() {
let text = KEYONLY;
let value = extract_all(text);
assert_eq!(value, &[("key".to_string(), String::new())]);
}
const KEY_INT: &str = "key=1";
#[test]
fn test_key_int() {
let text = KEY_INT;
let value = extract_all(text);
assert_eq!(value, &[("key".to_string(), "1".to_string())]);
}
const PREFIX_KEY_INT: &str = "pref.key=1";
#[test]
fn test_pref_key_int() {
let text = PREFIX_KEY_INT;
let value = extract_all(text);
assert_eq!(value, &[("pref.key".to_string(), "1".to_string())]);
}
const TABLED_KEY_INT: &str = "[pref]\nkey=1";
#[test]
fn test_tabled_key_int() {
let text = TABLED_KEY_INT;
let value = extract_all(text);
assert_eq!(value, &[("pref.key".to_string(), "1".to_string())]);
}
const MULTILINE: &str = "key = [\n1,\n2\n]";
#[test]
fn test_multiline() {
let text = MULTILINE;
let value = extract_all(text);
assert_eq!(value, &[("key".to_string(), "[1,2]".to_string())]);
}
#[test]
fn all_keys_empty() {
let text = "";
let keys = extract_all(text);
assert!(keys.is_empty());
}
#[test]
fn all_keys_nokeys() {
let text = "blabla";
let keys = extract_all(text);
assert!(keys.is_empty());
}
#[test]
fn all_keys_onekey() {
let text = "a = 1";
let keys = extract_all(text);
assert_eq!(keys, &[("a".to_string(), "1".to_string())]);
}
#[test]
fn all_keys_twokeys() {
let text = "a = 1\nb=2";
let keys = extract_all(text);
assert_eq!(
keys,
&[
("a".to_string(), "1".to_string()),
("b".to_string(), "2".to_string())
]
);
}
#[test]
fn all_keys_prefixed_twokeys() {
let text = "[c]\na = 1\nb=2";
let keys = extract_all(text);
assert_eq!(
keys,
&[
("c.a".to_string(), "1".to_string()),
("c.b".to_string(), "2".to_string())
]
);
}
#[test]
fn prefix_inside() {
let text = "[dependencies]\nx = { features = [\"\"] }";
let prefix = prefix(text, (1, 21).into());
assert_eq!(prefix, "dependencies.x.features");
}
}