use jsonc_parser::cst::{
CstInputValue, CstNode, CstObject, CstObjectProp, CstRootNode, ObjectPropName,
};
use serde_json::Value;
use super::keys::{Written, lookup};
use super::{Edit, Failure};
use crate::format::jsonc;
pub(super) fn semantic(text: &str) -> Result<Value, Failure> {
let value: Option<Value> = jsonc_parser::parse_to_serde_value(text, &jsonc())
.map_err(|e| (Some(e.range().start..e.range().end), e.kind().to_string()))?;
match value {
None => Ok(Value::Object(serde_json::Map::new())),
Some(value @ Value::Object(_)) => Ok(value),
Some(_) => Err((None, "the document is not an object, so it has no keys".to_owned())),
}
}
pub(super) fn apply(text: &str, edits: &[Edit<'_>], payload: &Written) -> Result<String, Failure> {
let parsed = CstRootNode::parse(&payload.source, &jsonc()).ok();
let Some((written, values)) = parsed.zip(semantic(&payload.source).ok()) else {
return edit(text, edits, None);
};
let after = semantic(&edit(text, edits, None)?)?;
edit(text, edits, Some(&Layout { written, values, after, source: &payload.source }))
}
struct Layout<'a> {
written: CstRootNode,
values: Value,
after: Value,
source: &'a str,
}
impl Layout<'_> {
fn writing<'k>(
&self, object: &CstObject, key: &'k [String],
) -> Option<(&'k [String], Writing)> {
let lacks = (1..=key.len())
.filter_map(|len| key.get(..len))
.find(|part| property(object, part).is_none());
lacks.into_iter().chain([key]).find_map(|part| {
let value = lookup(&self.values, part)?;
if lookup(&self.after, part) != Some(value) {
return None;
}
let written = property(&self.written.object_value()?, part)?;
Some((part, Writing::of(&written)?))
})
}
}
struct Writing {
text: String,
indent: Option<String>,
}
impl Writing {
fn of(property: &CstObjectProp) -> Option<Self> {
Some(Self { text: property.value()?.to_string(), indent: property.indent_text() })
}
}
struct Marks {
prefix: String,
writings: Vec<Writing>,
}
impl Marks {
fn new(text: &str, source: &str) -> Self {
let mut prefix = String::from("devset-written-");
while text.contains(&prefix) || source.contains(&prefix) {
prefix.push('-');
}
Self { prefix, writings: Vec::new() }
}
fn mark(&mut self, writing: Writing) -> CstInputValue {
let marker = format!("{}{}", self.prefix, self.writings.len());
self.writings.push(writing);
CstInputValue::String(marker)
}
fn spans_lines(&self, value: &str) -> bool {
let number = value
.strip_prefix(&format!("\"{}", self.prefix))
.and_then(|rest| rest.strip_suffix('"'));
let writing =
number.and_then(|n| n.parse::<usize>().ok()).and_then(|n| self.writings.get(n));
writing.is_some_and(|writing| writing.text.contains('\n'))
}
fn fill(self, mut out: String) -> String {
for (number, writing) in self.writings.into_iter().enumerate().rev() {
let marker = format!("\"{}{number}\"", self.prefix);
let Some(at) = out.find(&marker) else {
continue;
};
let line = out.get(..at).and_then(|before| before.rsplit('\n').next()).unwrap_or("");
let indent: String = line.chars().take_while(|c| matches!(c, ' ' | '\t')).collect();
let text = match writing.indent {
Some(from) => writing.text.replace(&format!("\n{from}"), &format!("\n{indent}")),
None => writing.text,
};
out = out.replacen(&marker, &text, 1);
}
out
}
}
fn edit(text: &str, edits: &[Edit<'_>], layout: Option<&Layout<'_>>) -> Result<String, Failure> {
let root = CstRootNode::parse(text, &jsonc())
.map_err(|e| (Some(e.range().start..e.range().end), e.kind().to_string()))?;
let object = root.object_value_or_set();
let inline = inline(&object, &[]);
let mut marks = Marks::new(text, layout.map_or("", |layout| layout.source));
let mut whole: Vec<&[String]> = Vec::new();
for &(key, value) in edits {
if whole.iter().any(|part| key.starts_with(part)) {
continue;
}
let Some(value) = value else {
remove(&object, key);
continue;
};
match layout.and_then(|layout| layout.writing(&object, key)) {
Some((part, writing)) => {
set(&object, part, marks.mark(writing));
whole.push(part);
},
None => set(&object, key, input(value)),
}
}
for (key, padded) in inline.iter().rev() {
if let Some(writing) = collapsed(&object, key, *padded, &marks) {
set(&object, key, marks.mark(writing));
}
}
Ok(marks.fill(root.to_string()))
}
fn inline(object: &CstObject, prefix: &[String]) -> Vec<(Vec<String>, bool)> {
let mut found = Vec::new();
for property in object.properties() {
let (Some(name), Some(inner)) = (property.decoded_name(), property.object_value()) else {
continue;
};
let key: Vec<String> = prefix.iter().cloned().chain([name]).collect();
let text = inner.to_string();
if !text.contains('\n') && !inner.properties().is_empty() {
found.push((key.clone(), text.starts_with("{ ")));
}
found.extend(inline(&inner, &key));
}
found
}
fn collapsed(object: &CstObject, key: &[String], padded: bool, marks: &Marks) -> Option<Writing> {
let inner = property(object, key)?.object_value()?;
if !inner.to_string().contains('\n') || inner.children().iter().any(CstNode::is_comment) {
return None;
}
let mut entries = Vec::new();
for property in inner.properties() {
let name = match property.name()? {
ObjectPropName::String(name) => name.to_string(),
ObjectPropName::Word(name) => name.to_string(),
};
let value = property.value()?.to_string();
if value.contains('\n') || marks.spans_lines(&value) {
return None;
}
entries.push(format!("{name}: {value}"));
}
let (open, close) = if padded { ("{ ", " }") } else { ("{", "}") };
Some(Writing { text: format!("{open}{}{close}", entries.join(", ")), indent: None })
}
fn property(object: &CstObject, key: &[String]) -> Option<CstObjectProp> {
let (leaf, parents) = key.split_last()?;
let parent =
parents.iter().try_fold(object.clone(), |object, segment| object.object_value(segment))?;
parent.get(leaf)
}
fn set(object: &CstObject, key: &[String], value: CstInputValue) {
let Some((leaf, parents)) = key.split_last() else {
return;
};
let parent =
parents.iter().fold(object.clone(), |object, segment| object.object_value_or_set(segment));
match parent.get(leaf) {
Some(property) => property.set_value(value),
None => {
parent.append(leaf, value);
},
}
}
fn remove(object: &CstObject, key: &[String]) {
let Some((first, rest)) = key.split_first() else {
return;
};
let Some(property) = object.get(first) else {
return;
};
if rest.is_empty() {
property.remove();
return;
}
let Some(inner) = property.object_value() else {
return;
};
remove(&inner, rest);
if inner.properties().is_empty() {
property.remove();
}
}
fn input(value: &Value) -> CstInputValue {
match value {
Value::Null => CstInputValue::Null,
Value::Bool(b) => CstInputValue::Bool(*b),
Value::Number(n) => CstInputValue::Number(n.to_string()),
Value::String(s) => CstInputValue::String(s.clone()),
Value::Array(items) => CstInputValue::Array(items.iter().map(input).collect()),
Value::Object(object) => {
CstInputValue::Object(object.iter().map(|(k, v)| (k.clone(), input(v))).collect())
},
}
}