jsonpiler 0.11.0

a Json syntax programming language for Windows
Documentation
use super::super::*;
use crate::prelude::*;
impl Server {
  pub(crate) fn m_definition(&mut self, params: JsonNoPos, id: IdKind) {
    let definition = (|| {
      let (jsonpiler, info, _) = self.prepare_symbol_lookup(params)?;
      Some(jsonpiler.pos2location(info.def?))
    })();
    self.response(id, definition.unwrap_or(NullN));
  }
  pub(crate) fn m_hover(&mut self, params: JsonNoPos, id: IdKind) {
    let hover = (|| {
      let (jsonpiler, info, pos) = self.prepare_symbol_lookup(params)?;
      let content = if info.kind == BuiltInFunc {
        self
          .docs
          .get(&info.name)
          .cloned()
          .unwrap_or_else(|| format!("No documentation for `{}`", info.name))
      } else {
        format!(
          "```jspl\n{}\n```\n{}\n",
          match info.kind {
            Argument => format!("{{ {}: {} }}", info.name, info.json_type),
            GlobalVar => format!("global({}: {}) = _", info.name, info.json_type),
            LocalVar => format!("let({}: {}) = _", info.name, info.json_type),
            BuiltInFunc | UserDefinedFunc =>
              if let FuncT(sig) = &info.json_type {
                let args = sig
                  .params
                  .iter()
                  .map(|(name, json_type)| format!("{name}: {json_type}"))
                  .collect::<Vec<_>>()
                  .join(", ");
                format!("{}({args}) -> {}", info.name, sig.ret_type)
              } else {
                format!("{}(?) -> ?", info.name)
              },
          },
          info.kind
        )
      };
      let range = pos2range(&jsonpiler.files[pos.file].parser.val.text, pos);
      Some(ObjectN(vec![
        (
          "contents".into(),
          ObjectN(vec![("kind".into(), StrN("markdown".into())), ("value".into(), StrN(content))]),
        ),
        ("range".into(), range),
      ]))
    })();
    self.response(id, hover.unwrap_or(NullN));
  }
  pub(crate) fn m_prepare_rename(&mut self, params: JsonNoPos, id: IdKind) {
    let definition = (|| {
      let (jsonpiler, info, pos) = self.prepare_symbol_lookup(params)?;
      if info.kind == BuiltInFunc {
        let log =
          LogMsg::new(MsgType::Warning, "Built-in function cannot be renamed.".into(), None);
        return Some(Err((-32602, log)));
      }
      let file = &jsonpiler.files[pos.file];
      Some(Ok(ObjectN(vec![
        ("range".into(), pos2range(&file.parser.val.text, pos)),
        ("placeholder".into(), StrN(info.name.clone())),
      ])))
    })()
    .unwrap_or(Ok(NullN));
    match definition {
      Ok(def) => self.response(id, def),
      Err((code, log)) => self.error(id, code, log),
    }
  }
  pub(crate) fn m_references(&mut self, params: JsonNoPos, id: IdKind) {
    let includes_decl =
      (|| params.get("context")?.get_bool("includeDeclaration"))().unwrap_or(true);
    let refs = (|| {
      let (jsonpiler, info, _) = self.prepare_symbol_lookup(params)?;
      let refs = if includes_decl { info.def_refs() } else { info.refs.clone() }
        .into_iter()
        .map(|pos| jsonpiler.pos2location(pos))
        .collect();
      Some(refs)
    })()
    .unwrap_or_default();
    self.response(id, ArrayN(refs));
  }
  pub(crate) fn m_rename(&mut self, mut params: JsonNoPos, id: IdKind) {
    let result = (|| {
      let new_name = params.take_str("newName")?;
      if validate_new_name(&new_name).is_none() {
        let log = LogMsg::new(MsgType::Warning, "Invalid name".into(), Some(new_name));
        return Some(Err((-32602, log)));
      }
      let (jsonpiler, info, _) = self.prepare_symbol_lookup(params)?;
      let mut changes: BTreeMap<String, Vec<JsonNoPos>> = BTreeMap::new();
      for pos in info.def_refs() {
        let file = &jsonpiler.files[pos.file];
        let uri = path2uri(&file.path);
        let change = ObjectN(vec![
          ("range".into(), pos2range(&file.parser.val.text, pos)),
          ("newText".into(), StrN(new_name.clone())),
        ]);
        changes.entry(uri).or_default().push(change);
      }
      Some(Ok(ObjectN(vec![(
        "changes".into(),
        ObjectN(changes.into_iter().map(|(uri, edits)| (uri, ArrayN(edits))).collect()),
      )])))
    })()
    .unwrap_or(Ok(NullN));
    match result {
      Ok(edit) => self.response(id, edit),
      Err((code, log)) => self.error(id, code, log),
    }
  }
  fn prepare_symbol_lookup(
    &mut self,
    mut params: JsonNoPos,
  ) -> Option<(Jsonpiler, SymbolInfo, Position)> {
    let uri = params.take("textDocument")?.take_str("uri")?;
    let position = params.take("position")?;
    self.flush(&uri);
    let source = self.get_source(&uri)?;
    let offset = range2offset(&source.text, &position)?;
    let mut jsonpiler = Jsonpiler::new(true);
    let file = jsonpiler.push_file(source.text, uri2path(&uri)).ok()?;
    let parsed = file.parse_jspl().ok()?;
    jsonpiler.compile(parsed).ok()?;
    let info = jsonpiler.analysis.as_ref()?.find_symbol(offset)?.clone();
    let pos = *info.def_refs().iter().find(|pos| pos.contains_inclusive(0, offset as u32))?;
    Some((jsonpiler, info, pos))
  }
}
impl Jsonpiler {
  pub(crate) fn pos2location(&self, pos: Position) -> JsonNoPos {
    let file = &self.files[pos.file];
    ObjectN(vec![
      ("uri".into(), StrN(path2uri(&file.path))),
      ("range".into(), pos2range(&file.parser.val.text, pos)),
    ])
  }
}
impl Analysis {
  pub(crate) fn find_symbol(&self, offset: usize) -> Option<&SymbolInfo> {
    self.symbols.iter().find(|info| {
      info.def_refs().iter().any(|use_pos| use_pos.contains_inclusive(0, offset as u32))
        && !(info.name == "$" && info.kind == BuiltInFunc)
    })
  }
}
fn pos2range(text: &str, pos: Position) -> JsonNoPos {
  format_range(offset2range(text, pos.offset), offset2range(text, pos.end()))
}
fn validate_new_name(new_name: &str) -> Option<()> {
  let mut parser = <Pos<Parser>>::new(new_name.into(), 0);
  let parsed = parser.parse_jspl("newName.jspl").ok()?;
  if let Object(Lit(obj)) = parsed.val
    && obj.len() == 1
    && obj[0].0.val == "$"
  {
    if let Str(Lit(name)) = &obj[0].1.val {
      (name == new_name).then_some(())
    } else if let Array(_, Lit(array)) = &obj[0].1.val
      && let Str(Lit(name)) = &array[0].val
      && name == new_name
    {
      Some(())
    } else {
      None
    }
  } else {
    None
  }
}