use std::collections::HashMap;
use std::io::{BufRead, Write};
use anyhow::{Context, Result, bail};
use serde_json::{Value, json};
use crate::core;
pub fn serve() -> Result<()> {
let stdin = std::io::stdin();
let mut reader = stdin.lock();
let stdout = std::io::stdout();
let mut writer = stdout.lock();
let mut docs: HashMap<String, String> = HashMap::new();
let mut shutdown = false;
loop {
let msg = match read_message(&mut reader) {
Ok(Some(m)) => m,
Ok(None) => break, Err(e) => bail!("framing: {e}"),
};
let method = msg.get("method").and_then(Value::as_str).unwrap_or("");
let id = msg.get("id").cloned();
let params = msg.get("params").cloned().unwrap_or(Value::Null);
match method {
"initialize" => {
respond(&mut writer, id, json!({
"capabilities": {
"textDocumentSync": 1,
"completionProvider": {
"triggerCharacters": ["|", "/", ":", "$", "<", "-", "@"]
}
},
"serverInfo": {
"name": "quarb-lsp",
"version": env!("CARGO_PKG_VERSION")
}
}))?;
}
"initialized" => {}
"shutdown" => {
shutdown = true;
respond(&mut writer, id, Value::Null)?;
}
"exit" => {
std::process::exit(i32::from(!shutdown));
}
"textDocument/didOpen" => {
let uri = str_at(¶ms, &["textDocument", "uri"]);
let text = str_at(¶ms, &["textDocument", "text"]);
docs.insert(uri.clone(), text.clone());
publish(&mut writer, &uri, &text)?;
}
"textDocument/didChange" => {
let uri = str_at(¶ms, &["textDocument", "uri"]);
let text = params["contentChanges"]
.as_array()
.and_then(|c| c.last())
.and_then(|c| c["text"].as_str())
.unwrap_or("")
.to_string();
docs.insert(uri.clone(), text.clone());
publish(&mut writer, &uri, &text)?;
}
"textDocument/didClose" => {
docs.remove(&str_at(¶ms, &["textDocument", "uri"]));
}
"textDocument/completion" => {
let uri = str_at(¶ms, &["textDocument", "uri"]);
let line = params["position"]["line"].as_u64().unwrap_or(0) as u32;
let character =
params["position"]["character"].as_u64().unwrap_or(0) as u32;
let empty = String::new();
let text = docs.get(&uri).unwrap_or(&empty);
let items: Vec<Value> = core::completions(text, line, character)
.into_iter()
.map(|i| {
json!({
"label": i.label,
"kind": lsp_kind(i.kind),
"detail": i.kind,
})
})
.collect();
respond(&mut writer, id, json!(items))?;
}
_ if id.is_some() => respond(&mut writer, id, Value::Null)?,
_ => {}
}
}
Ok(())
}
fn lsp_kind(kind: &str) -> u32 {
match kind {
"function" | "aggregate" => 3, "register" => 6, "child" => 5, "property" => 10, "trait" => 7, _ => 1,
}
}
fn str_at(v: &Value, path: &[&str]) -> String {
let mut cur = v;
for p in path {
cur = &cur[*p];
}
cur.as_str().unwrap_or("").to_string()
}
fn publish(w: &mut impl Write, uri: &str, text: &str) -> Result<()> {
let diags: Vec<Value> = core::diagnostics(text)
.into_iter()
.map(|d| {
json!({
"range": {
"start": {"line": d.line, "character": d.col_start},
"end": {"line": d.line, "character": d.col_end}
},
"severity": 1,
"source": "quarb",
"message": d.message
})
})
.collect();
notify(w, "textDocument/publishDiagnostics", json!({
"uri": uri,
"diagnostics": diags
}))
}
fn respond(w: &mut impl Write, id: Option<Value>, result: Value) -> Result<()> {
write_message(w, &json!({
"jsonrpc": "2.0",
"id": id.unwrap_or(Value::Null),
"result": result
}))
}
fn notify(w: &mut impl Write, method: &str, params: Value) -> Result<()> {
write_message(w, &json!({
"jsonrpc": "2.0",
"method": method,
"params": params
}))
}
fn write_message(w: &mut impl Write, v: &Value) -> Result<()> {
let body = serde_json::to_string(v)?;
write!(w, "Content-Length: {}\r\n\r\n{}", body.len(), body)?;
w.flush()?;
Ok(())
}
fn read_message(r: &mut impl BufRead) -> Result<Option<Value>> {
let mut content_length: Option<usize> = None;
loop {
let mut line = String::new();
if r.read_line(&mut line)? == 0 {
return Ok(None);
}
let line = line.trim_end();
if line.is_empty() {
break;
}
if let Some(v) = line.strip_prefix("Content-Length:") {
content_length = Some(v.trim().parse().context("Content-Length")?);
}
}
let len = content_length.context("missing Content-Length")?;
let mut buf = vec![0u8; len];
r.read_exact(&mut buf)?;
Ok(Some(serde_json::from_slice(&buf)?))
}