use std::io::{Read, Write};
use std::os::unix::net::{UnixListener, UnixStream};
use std::path::PathBuf;
use anyhow::{Context, Result};
use kaivrpc::error::RpcError;
use kaivrpc::request::Request;
use kaivrpc::response::{Envelope, Response};
use serde::{Deserialize, Serialize};
use crate::core::{self, Workspace};
#[derive(Deserialize)]
struct SymbolsParams {
text: Option<String>,
lang: Option<String>,
file: Option<String>,
}
#[derive(Deserialize)]
struct WordParams {
root: Option<String>,
file: Option<String>,
text: Option<String>,
word: String,
}
#[derive(Deserialize)]
struct QueryParams {
root: Option<String>,
file: Option<String>,
text: Option<String>,
query: String,
scope: Option<String>,
}
#[derive(Serialize)]
struct ValueRow {
value: String,
}
#[derive(Serialize)]
struct ValueRows {
rows: Vec<ValueRow>,
}
#[derive(Serialize)]
struct SymbolRows {
rows: Vec<core::Symbol>,
}
#[derive(Serialize)]
struct LocationRows {
rows: Vec<core::Location>,
}
#[derive(Serialize)]
struct HoverRows {
rows: Vec<core::HoverRow>,
}
pub fn serve(socket: &str) -> Result<()> {
let _ = std::fs::remove_file(socket);
let listener =
UnixListener::bind(socket).with_context(|| format!("binding {socket}"))?;
eprintln!("quarb-code-lsp: kaivrpc on {socket}");
for conn in listener.incoming() {
let mut conn = conn?;
if let Err(e) = handle(&mut conn) {
eprintln!("quarb-code-lsp: {e}");
}
}
Ok(())
}
fn handle(conn: &mut UnixStream) -> Result<()> {
let mut body = Vec::new();
conn.read_to_end(&mut body)?;
let out = answer(&body);
conn.write_all(out.as_bytes())?;
Ok(())
}
fn word_workspace(p: &WordParams) -> (Workspace, String) {
let mut ws = Workspace::new(p.root.as_ref().map(PathBuf::from));
let uri = match (&p.file, &p.text) {
(Some(f), Some(t)) => {
let uri = format!("file://{f}");
ws.open(&uri, t);
uri
}
(Some(f), None) => {
let uri = format!("file://{f}");
if let Ok(t) = std::fs::read_to_string(f) {
ws.open(&uri, &t);
}
uri
}
_ => String::new(),
};
(ws, uri)
}
pub fn answer(body: &[u8]) -> String {
let envelope = Envelope::kaivrpc();
let resp = Response::new(envelope, None, &[]);
match Request::parse(body) {
Err(e) => resp
.error(&RpcError::new(400, "bad-request", e.to_string()))
.unwrap_or_default(),
Ok(req) => match req.method() {
"quarb-code-lsp/symbols" => match req.params::<SymbolsParams>() {
Ok(p) => {
let mut ws = Workspace::new(None);
let uri = match (&p.file, &p.text) {
(Some(f), _) => {
let uri = format!("file://{f}");
if let Ok(t) = std::fs::read_to_string(f) {
ws.open(&uri, &t);
}
uri
}
(None, Some(t)) => {
let uri = format!(
"file://untitled.{}",
p.lang.as_deref().unwrap_or("")
);
ws.open(&uri, t);
uri
}
(None, None) => String::new(),
};
let rows = ws.symbols(&uri);
let n = rows.len();
resp.buffered(&SymbolRows { rows }, n).unwrap_or_default()
}
Err(e) => bad_params(&resp, e),
},
"quarb-code-lsp/definition" => match req.params::<WordParams>() {
Ok(p) => {
let (ws, uri) = word_workspace(&p);
let rows = ws.definition(&uri, &p.word);
let n = rows.len();
resp.buffered(&LocationRows { rows }, n).unwrap_or_default()
}
Err(e) => bad_params(&resp, e),
},
"quarb-code-lsp/references" => match req.params::<WordParams>() {
Ok(p) => {
let (ws, uri) = word_workspace(&p);
let rows = ws.references(&uri, &p.word);
let n = rows.len();
resp.buffered(&LocationRows { rows }, n).unwrap_or_default()
}
Err(e) => bad_params(&resp, e),
},
"quarb-code-lsp/hover" => match req.params::<WordParams>() {
Ok(p) => {
let (ws, uri) = word_workspace(&p);
let rows = ws.hover(&uri, &p.word);
let n = rows.len();
resp.buffered(&HoverRows { rows }, n).unwrap_or_default()
}
Err(e) => bad_params(&resp, e),
},
"quarb-code-lsp/query" => match req.params::<QueryParams>() {
Ok(p) => {
let wp = WordParams {
root: p.root,
file: p.file,
text: p.text,
word: String::new(),
};
let (ws, uri) = word_workspace(&wp);
let file_only = p.scope.as_deref() == Some("file");
match ws.query(&uri, &p.query, file_only) {
core::QueryAnswer::Locations(rows) => {
let n = rows.len();
resp.buffered(&LocationRows { rows }, n).unwrap_or_default()
}
core::QueryAnswer::Values(vals) => {
let rows: Vec<ValueRow> =
vals.into_iter().map(|value| ValueRow { value }).collect();
let n = rows.len();
resp.buffered(&ValueRows { rows }, n).unwrap_or_default()
}
core::QueryAnswer::Refused(msg) => resp
.error(&RpcError::new(422, "refused", msg))
.unwrap_or_default(),
}
}
Err(e) => bad_params(&resp, e),
},
other => resp
.error(&RpcError::new(
404,
"no-such-method",
format!("unknown method {other:?}"),
))
.unwrap_or_default(),
},
}
}
fn bad_params(resp: &Response, e: impl std::fmt::Display) -> String {
resp.error(&RpcError::new(422, "bad-params", e.to_string()))
.unwrap_or_default()
}