use serde_json::Value;
use crate::editor::Cursor;
use crate::lsp::{
self, CodeAction, CompletionItem, Diagnostic, Hover, Location, SignatureHelp, WorkspaceEdit,
};
use super::{LspEventOutcome, LspRequestKind};
pub(super) struct Group {
pub(super) remaining: usize,
pub(super) accum: GroupAccum,
}
pub(super) enum GroupAccum {
Jump {
label: &'static str,
locations: Vec<Location>,
},
References(Vec<Location>),
Rename {
new_name: String,
edit: Option<WorkspaceEdit>,
},
CodeAction(Vec<CodeAction>),
Hover(Vec<String>),
Completion {
prefix_start: Cursor,
items: Vec<CompletionItem>,
},
CompletionResolve {
uri: String,
item_index: Option<usize>,
item: Option<CompletionItem>,
},
CodeActionResolve {
action: Option<CodeAction>,
},
SignatureHelp {
anchor_row: usize,
help: Option<SignatureHelp>,
},
}
pub(super) fn accumulate(
accum: &mut GroupAccum,
source: &str,
result: &Value,
kind: &LspRequestKind,
) {
match (accum, kind) {
(GroupAccum::Jump { locations, .. }, LspRequestKind::Jump) => {
locations.extend(lsp::parse_locations(result));
}
(GroupAccum::References(locations), LspRequestKind::References) => {
locations.extend(lsp::parse_locations(result));
}
(GroupAccum::Rename { edit, .. }, LspRequestKind::Rename) if edit.is_none() => {
*edit = lsp::parse_workspace_edit(result);
}
(GroupAccum::CodeAction(actions), LspRequestKind::CodeAction) => {
let mut parsed = lsp::parse_code_actions(result);
for a in &mut parsed {
a.source = source.to_string();
}
actions.extend(parsed);
}
(GroupAccum::Hover(parts), LspRequestKind::Hover) => {
if let Some(h) = lsp::parse_hover(result) {
parts.push(h.contents);
}
}
(GroupAccum::Completion { items, .. }, LspRequestKind::Completion) => {
let mut parsed = lsp::parse_completion(result);
for it in &mut parsed {
it.source = source.to_string();
}
items.extend(parsed);
}
(GroupAccum::CompletionResolve { item, .. }, LspRequestKind::CompletionResolve) => {
*item = lsp::parse_completion_resolve(result);
if let Some(it) = item.as_mut() {
it.source = source.to_string();
}
}
(GroupAccum::CodeActionResolve { action }, LspRequestKind::CodeActionResolve) => {
let mut parsed = lsp::parse_code_action(result);
if let Some(a) = parsed.as_mut() {
a.source = source.to_string();
}
*action = parsed;
}
(GroupAccum::SignatureHelp { help, .. }, LspRequestKind::SignatureHelp)
if help.is_none() =>
{
*help = lsp::parse_signature_help(result);
}
_ => {}
}
}
pub(super) fn finalize(accum: GroupAccum) -> LspEventOutcome {
match accum {
GroupAccum::Jump { label, locations } => LspEventOutcome::Jump { label, locations },
GroupAccum::References(locations) => LspEventOutcome::References(locations),
GroupAccum::Rename { new_name, edit } => LspEventOutcome::Rename { new_name, edit },
GroupAccum::CodeAction(actions) => LspEventOutcome::CodeActions(actions),
GroupAccum::Hover(parts) => {
if parts.is_empty() {
LspEventOutcome::Hover(None)
} else {
LspEventOutcome::Hover(Some(Hover {
contents: parts.join("\n\n---\n\n"),
}))
}
}
GroupAccum::Completion {
prefix_start,
items,
} => {
let items = dedup_completion(items);
LspEventOutcome::Completion {
prefix_start,
items,
}
}
GroupAccum::CompletionResolve {
uri,
item_index,
item,
} => LspEventOutcome::CompletionResolved {
uri,
item_index,
item,
},
GroupAccum::CodeActionResolve { action } => LspEventOutcome::CodeActionResolved(action),
GroupAccum::SignatureHelp { anchor_row, help } => {
LspEventOutcome::SignatureHelp { anchor_row, help }
}
}
}
pub(super) fn signature_help_to_json(help: &SignatureHelp) -> Value {
let signatures: Vec<Value> = help
.signatures
.iter()
.map(|s| {
let parameters: Vec<Value> = s
.parameters
.iter()
.map(|p| match &p.label {
lsp::ParameterLabel::Text(t) => serde_json::json!({ "label": t }),
lsp::ParameterLabel::Offsets(start, end) => {
serde_json::json!({ "label": [start, end] })
}
})
.collect();
let mut obj = serde_json::json!({
"label": s.label,
"parameters": parameters,
});
if let Some(ap) = s.active_parameter {
obj["activeParameter"] = Value::from(ap);
}
obj
})
.collect();
let mut obj = serde_json::json!({
"signatures": signatures,
"activeSignature": help.active_signature,
});
obj["activeParameter"] = match help.active_parameter {
Some(n) => Value::from(n),
None => Value::Null,
};
obj
}
pub(super) fn dedup_completion(items: Vec<CompletionItem>) -> Vec<CompletionItem> {
use std::collections::HashSet;
let mut seen: HashSet<(String, u8, String)> = HashSet::new();
let mut out = Vec::with_capacity(items.len());
for it in items {
let text_key = it
.text_edit
.as_ref()
.map(|te| te.new_text.clone())
.or_else(|| it.insert_text.clone())
.unwrap_or_else(|| it.label.clone());
let key = (it.label.clone(), it.kind, text_key);
if seen.insert(key) {
out.push(it);
}
}
out
}
pub(super) fn diagnostic_to_json(d: &Diagnostic) -> Value {
serde_json::json!({
"range": {
"start": { "line": d.range.start.line, "character": d.range.start.character },
"end": { "line": d.range.end.line, "character": d.range.end.character },
},
"severity": d.severity as u8 + 1,
"message": d.message,
"source": d.source,
})
}