use std::borrow::ToOwned;
use anyhow::Result;
use lsp::CompletionItem;
use lsp::CompletionItemKind;
use lsp::CompletionItemLabelDetails;
use lsp::CompletionTextEdit;
use lsp::Documentation;
use lsp::MarkupContent;
use lsp::MarkupKind;
use lsp::TextEdit;
use crate::alloc::fmt::TryWrite;
use crate::alloc::prelude::*;
use crate::alloc::{String, Vec};
use crate::compile::meta;
use crate::runtime::debug::DebugArgs;
use crate::Context;
use crate::Unit;
use super::state::ServerSource;
pub(super) fn complete_for_unit(
workspace_source: &ServerSource,
unit: &Unit,
symbol: &str,
position: lsp::Position,
results: &mut Vec<CompletionItem>,
) -> Result<()> {
let Some(debug_info) = unit.debug_info() else {
return Ok(());
};
for (hash, function) in debug_info.functions.iter() {
let func_name = function.try_to_string()?;
if !func_name.starts_with(symbol) {
continue;
}
let Some(last) = function.path.base_name() else {
continue;
};
let args = match &function.args {
DebugArgs::EmptyArgs => None,
DebugArgs::TupleArgs(n) => Some({
let mut o = String::new();
let mut it = 0..*n;
let last = it.next_back();
for n in it {
write!(o, "_{n}, ")?;
}
if let Some(n) = last {
write!(o, "_{n}")?;
}
o
}),
DebugArgs::Named(names) => Some(names.iter().map(|s| s.as_ref()).try_join(", ")?),
};
let docs = workspace_source
.get_docs_by_hash(*hash)
.map(|docs| docs.docs.join("\n"));
let detail = args.map(|a| format!("({a:}) -> ?"));
results.try_push(CompletionItem {
label: last.to_owned(),
kind: Some(CompletionItemKind::FUNCTION),
detail: detail.clone(),
documentation: docs.map(|d| {
Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: d,
})
}),
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
range: lsp::Range {
start: lsp::Position {
line: position.line,
character: position.character - symbol.len() as u32,
},
end: position,
},
new_text: format!("{}", function.path),
})),
label_details: Some(CompletionItemLabelDetails {
detail,
description: None,
}),
commit_characters: Some(vec!["(".into()]),
..Default::default()
})?;
}
Ok(())
}
pub(super) fn complete_native_instance_data(
context: &Context,
symbol: &str,
position: lsp::Position,
results: &mut Vec<CompletionItem>,
) -> Result<()> {
for (meta, signature) in context.iter_functions() {
let (prefix, kind, n) = match (&meta.item, &meta.kind) {
(
Some(item),
meta::Kind::Function {
associated: Some(meta::AssociatedKind::Instance(name)),
..
},
) => (item, CompletionItemKind::FUNCTION, name),
_ => continue,
};
if n.starts_with(symbol) {
let return_type = signature
.return_type
.base
.as_non_empty()
.and_then(|hash| context.lookup_meta_by_hash(hash).next())
.and_then(|r| r.item.as_deref());
let docs = meta.docs.lines().join("\n");
let args = meta.docs.args().join(", ");
let detail = return_type.map(|r| format!("({args}) -> {r}"));
let Ok(data) = serde_json::to_value(meta.hash.into_inner()) else {
continue;
};
results.try_push(CompletionItem {
label: n.try_to_string()?.into_std(),
kind: Some(kind),
detail,
documentation: Some(lsp::Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: docs,
})),
text_edit: Some(CompletionTextEdit::Edit(TextEdit {
range: lsp::Range {
start: lsp::Position {
line: position.line,
character: position.character - symbol.len() as u32,
},
end: position,
},
new_text: n.try_to_string()?.into_std(),
})),
label_details: Some(CompletionItemLabelDetails {
detail: None,
description: Some(prefix.try_to_string()?.into_std()),
}),
data: Some(data),
..Default::default()
})?;
}
}
Ok(())
}
pub(super) fn complete_native_loose_data(
context: &Context,
symbol: &str,
position: lsp::Position,
results: &mut Vec<CompletionItem>,
) -> Result<()> {
for (meta, signature) in context.iter_functions() {
let (item, kind) = match (&meta.item, &meta.kind) {
(Some(item), meta::Kind::Function { .. }) => (item, CompletionItemKind::FUNCTION),
_ => continue,
};
let func_name = item
.try_to_string()?
.trim_start_matches("::")
.try_to_owned()?;
if func_name.starts_with(symbol) {
let return_type = signature
.return_type
.base
.as_non_empty()
.and_then(|hash| context.lookup_meta_by_hash(hash).next())
.and_then(|r| r.item.as_deref());
let docs = meta.docs.lines().join("\n");
let args = meta.docs.args().join(", ");
let detail = return_type.map(|r| format!("({args}) -> {r}"));
let Ok(data) = serde_json::to_value(meta.hash.into_inner()) else {
continue;
};
results.try_push(CompletionItem {
label: func_name.try_clone()?.into_std(),
kind: Some(kind),
detail,
documentation: Some(lsp::Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::Markdown,
value: docs,
})),
text_edit: Some(lsp::CompletionTextEdit::Edit(TextEdit {
range: lsp::Range {
start: lsp::Position {
line: position.line,
character: position.character - symbol.len() as u32,
},
end: position,
},
new_text: func_name.into_std(),
})),
data: Some(data),
..Default::default()
})?;
}
}
Ok(())
}