use serde_json::Value;
use super::{parse_range, parse_text_edit};
use crate::lsp::types::{
CompletionItem, Hover, ParameterInformation, ParameterLabel, SignatureHelp,
SignatureInformation, TextEdit,
};
pub fn parse_hover(v: &Value) -> Option<Hover> {
if v.is_null() {
return None;
}
let contents = v.get("contents")?;
let joined = collect_hover_contents(contents);
let trimmed = joined.trim();
if trimmed.is_empty() {
return None;
}
Some(Hover {
contents: trimmed.to_string(),
})
}
fn collect_hover_contents(v: &Value) -> String {
if let Some(obj) = v.as_object()
&& let Some(value) = obj.get("value").and_then(|x| x.as_str())
&& obj.get("kind").is_some()
{
return value.to_string();
}
if let Some(s) = v.as_str() {
return s.to_string();
}
if let Some(obj) = v.as_object()
&& let Some(value) = obj.get("value").and_then(|x| x.as_str())
{
let lang = obj.get("language").and_then(|x| x.as_str()).unwrap_or("");
return format!("```{}\n{}\n```", lang, value);
}
if let Some(arr) = v.as_array() {
let parts: Vec<String> = arr
.iter()
.map(collect_hover_contents)
.filter(|s| !s.trim().is_empty())
.collect();
return parts.join("\n\n");
}
String::new()
}
pub fn parse_completion(v: &Value) -> Vec<CompletionItem> {
if v.is_null() {
return Vec::new();
}
let arr = if let Some(a) = v.as_array() {
a.as_slice()
} else if let Some(a) = v.get("items").and_then(|x| x.as_array()) {
a.as_slice()
} else {
return Vec::new();
};
arr.iter().filter_map(parse_completion_item).collect()
}
fn parse_completion_item(v: &Value) -> Option<CompletionItem> {
let label = v.get("label")?.as_str()?.to_string();
let kind = v.get("kind").and_then(|x| x.as_u64()).unwrap_or(0) as u8;
let text_edit = v.get("textEdit").and_then(|te| {
let new_text = te.get("newText")?.as_str()?.to_string();
let range = te
.get("range")
.or_else(|| te.get("replace"))
.or_else(|| te.get("insert"))?;
let range = parse_range(range)?;
Some(TextEdit { range, new_text })
});
let insert_text = v
.get("insertText")
.and_then(|x| x.as_str())
.map(|s| s.to_string());
let filter_text = v
.get("filterText")
.and_then(|x| x.as_str())
.map(|s| s.to_string());
let sort_text = v
.get("sortText")
.and_then(|x| x.as_str())
.map(|s| s.to_string());
let detail = v
.get("detail")
.and_then(|x| x.as_str())
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.or_else(|| {
let ld = v.get("labelDetails")?;
let suffix = ld.get("detail").and_then(|x| x.as_str()).unwrap_or("");
let desc = ld.get("description").and_then(|x| x.as_str()).unwrap_or("");
let combined = match (suffix.is_empty(), desc.is_empty()) {
(true, true) => return None,
(false, true) => suffix.to_string(),
(true, false) => desc.to_string(),
(false, false) => format!("{} {}", suffix, desc),
};
Some(combined)
})
.or_else(|| kind_word(kind).map(|s| s.to_string()));
let additional_text_edits = v
.get("additionalTextEdits")
.and_then(|x| x.as_array())
.map(|arr| arr.iter().filter_map(parse_text_edit).collect())
.unwrap_or_default();
Some(CompletionItem {
label,
kind,
text_edit,
insert_text,
filter_text,
sort_text,
detail,
resolved_detail: None,
additional_text_edits,
raw: v.clone(),
source: String::new(),
})
}
pub fn parse_completion_resolve(v: &Value) -> Option<CompletionItem> {
parse_completion_item(v)
}
fn kind_word(kind: u8) -> Option<&'static str> {
match kind {
1 => Some("text"),
2 => Some("method"),
3 => Some("func"),
4 => Some("constructor"),
5 => Some("field"),
6 => Some("var"),
7 => Some("class"),
8 => Some("interface"),
9 => Some("module"),
10 => Some("property"),
11 => Some("unit"),
12 => Some("value"),
13 => Some("enum"),
14 => Some("keyword"),
15 => Some("snippet"),
16 => Some("color"),
17 => Some("file"),
18 => Some("reference"),
19 => Some("folder"),
20 => Some("enum member"),
21 => Some("const"),
22 => Some("struct"),
23 => Some("event"),
24 => Some("operator"),
25 => Some("type param"),
_ => None,
}
}
pub fn parse_signature_help(v: &Value) -> Option<SignatureHelp> {
if v.is_null() {
return None;
}
let sigs_raw = v.get("signatures").and_then(|x| x.as_array())?;
let signatures: Vec<SignatureInformation> =
sigs_raw.iter().filter_map(parse_signature_info).collect();
if signatures.is_empty() {
return None;
}
let active_signature = v
.get("activeSignature")
.and_then(|x| x.as_u64())
.unwrap_or(0) as usize;
let active_signature = active_signature.min(signatures.len() - 1);
let active_parameter = match v.get("activeParameter") {
Some(Value::Null) => None,
Some(x) => x.as_u64().map(|n| n as usize),
None => Some(0),
};
Some(SignatureHelp {
signatures,
active_signature,
active_parameter,
})
}
fn parse_signature_info(v: &Value) -> Option<SignatureInformation> {
let label = v.get("label")?.as_str()?.to_string();
let parameters = v
.get("parameters")
.and_then(|x| x.as_array())
.map(|arr| arr.iter().filter_map(parse_parameter_info).collect())
.unwrap_or_default();
let active_parameter = match v.get("activeParameter") {
Some(Value::Null) => None,
Some(x) => x.as_u64().map(|n| n as usize),
None => None,
};
Some(SignatureInformation {
label,
parameters,
active_parameter,
})
}
fn parse_parameter_info(v: &Value) -> Option<ParameterInformation> {
let label_v = v.get("label")?;
let label = if let Some(s) = label_v.as_str() {
ParameterLabel::Text(s.to_string())
} else if let Some(arr) = label_v.as_array() {
let start = arr.first()?.as_u64()? as u32;
let end = arr.get(1)?.as_u64()? as u32;
ParameterLabel::Offsets(start, end)
} else {
return None;
};
Some(ParameterInformation { label })
}