use anyhow::Result;
use anyhow::bail;
use lsp_types::InlayHint;
use lsp_types::InlayHintKind;
use lsp_types::InlayHintLabel;
use lsp_types::Position;
use lsp_types::Range;
use lsp_types::TextEdit;
use rowan::TextSize;
use url::Url;
use wdl_ast::AstToken;
use crate::graph::DocumentGraph;
use crate::graph::ParseState;
use crate::handlers::common::position;
use crate::types::CustomType;
use crate::types::Type;
fn position_in_range(pos: &Position, range: &Range) -> bool {
(pos.line > range.start.line
|| (pos.line == range.start.line && pos.character >= range.start.character))
&& (pos.line < range.end.line
|| (pos.line == range.end.line && pos.character <= range.end.character))
}
pub fn inlay_hints(
graph: &DocumentGraph,
uri: &Url,
range: Range,
) -> Result<Option<Vec<InlayHint>>> {
let Some(index) = graph.get_index(uri) else {
bail!("document `{uri}` not found in graph.");
};
let node = graph.get(index);
let lines = match node.parse_state() {
ParseState::Parsed { lines, .. } => lines.clone(),
_ => bail!("document `{uri}` has not been parsed", uri = uri),
};
let Some(document) = node.document() else {
bail!("document analysis data not available for {}", uri);
};
let mut hints = Vec::new();
for (_, enum_entry) in document.enums() {
if enum_entry.namespace().is_some() {
continue;
}
let definition = enum_entry.definition();
let name_span = definition.name().span();
let absolute_end = enum_entry.offset() + name_span.end();
let enum_name_end_pos = position(&lines, TextSize::try_from(absolute_end)?)?;
if !position_in_range(&enum_name_end_pos, &range) {
continue;
}
if definition.type_parameter().is_none() {
let Some(enum_type) = enum_entry.ty() else {
continue;
};
let CustomType::Enum(enum_type) = enum_type.as_custom().unwrap() else {
continue;
};
let inner_type = enum_type.inner_value_type();
if !matches!(inner_type, Type::Union) {
hints.push(InlayHint {
position: Position {
line: enum_name_end_pos.line,
character: enum_name_end_pos.character,
},
label: InlayHintLabel::String(format!("[{}]", inner_type)),
kind: Some(InlayHintKind::TYPE),
text_edits: Some(vec![TextEdit {
range: Range {
start: enum_name_end_pos,
end: enum_name_end_pos,
},
new_text: format!("[{}]", inner_type),
}]),
tooltip: Some(lsp_types::InlayHintTooltip::String(
"Click to insert type parameter".to_string(),
)),
padding_left: None,
padding_right: None,
data: None,
});
}
}
for variant in definition.variants() {
if variant.value().is_some() {
continue;
}
let variant_name = variant.name().text().to_string();
let variant_span = variant.name().span();
let absolute_end = enum_entry.offset() + variant_span.end();
let variant_end_pos = position(&lines, TextSize::try_from(absolute_end)?)?;
if !position_in_range(&variant_end_pos, &range) {
continue;
}
hints.push(InlayHint {
position: Position {
line: variant_end_pos.line,
character: variant_end_pos.character,
},
label: InlayHintLabel::String(format!(" = \"{}\"", variant_name)),
kind: Some(InlayHintKind::PARAMETER),
text_edits: Some(vec![TextEdit {
range: Range {
start: variant_end_pos,
end: variant_end_pos,
},
new_text: format!(" = \"{}\"", variant_name),
}]),
tooltip: Some(lsp_types::InlayHintTooltip::String(
"Click to insert variant value".to_string(),
)),
padding_left: None,
padding_right: None,
data: None,
});
}
}
Ok(Some(hints))
}