use cpp_code_analysis::{FileView, ParameterHint, VfsFile};
use lsp_types::{
ClientCapabilities, InlayHint, InlayHintKind, InlayHintLabel, InlayHintParams,
InlayHintServerCapabilities, OneOf, ServerCapabilities,
};
use tokio_util::sync::CancellationToken;
use super::RegisterCapabilities;
use crate::context::{RequestOutcome, ServerContextSnapshot, snapshot_query};
use crate::util::{offset_at_position, position_in_file, uri_to_file_path};
pub async fn on_inlay_hint(
context: ServerContextSnapshot,
params: InlayHintParams,
cancel_token: CancellationToken,
) -> RequestOutcome<Vec<InlayHint>> {
let uri = params.text_document.uri;
let requested = params.range;
let started = std::time::Instant::now();
if let Some(path) = uri_to_file_path(&uri) {
context.analysis().prepare(&path).await;
context.analysis().catch_up(&path).await;
if cpp_code_analysis::probes::inlay() {
eprintln!("[inlay] prepared and caught up in {:?}", started.elapsed());
}
}
snapshot_query(context.analysis(), cancel_token, move |session| {
let path = uri_to_file_path(&uri)?;
let view = session.view_of_the_file(&path)?;
let parsed = started.elapsed();
let held = session.files().held(&view.path)?;
let hints = session.inlay_hints(&view, visible_range(&view, requested)?);
if cpp_code_analysis::probes::inlay() {
eprintln!(
"[inlay] view {parsed:?}, hints {:?}, {} hint(s)",
started.elapsed() - parsed,
hints.len()
);
}
Some(
hints
.into_iter()
.filter_map(|hint| inlay_hint_of(held, hint))
.collect(),
)
})
.await
}
fn visible_range(view: &FileView, range: lsp_types::Range) -> Option<cpp_parser::SourceRange> {
let len = view.source.len();
let start = offset_at_position(view, range.start).unwrap_or(
cpp_code_analysis::file::coordinates::ReadingOffset::new(len),
);
let end = offset_at_position(view, range.end).unwrap_or(
cpp_code_analysis::file::coordinates::ReadingOffset::new(len),
);
(start <= end).then(|| cpp_parser::SourceRange::new(start.get(), end.get() - start.get()))
}
pub fn inlay_hint_of(file: &VfsFile, hint: ParameterHint) -> Option<InlayHint> {
let position = position_in_file(file, hint.offset)?;
Some(InlayHint {
position,
label: InlayHintLabel::String(format!("{}:", hint.name)),
kind: Some(InlayHintKind::PARAMETER),
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: Some(true),
data: None,
})
}
pub struct InlayHintCapabilities;
impl RegisterCapabilities for InlayHintCapabilities {
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
server_capabilities.inlay_hint_provider = Some(OneOf::Right(
InlayHintServerCapabilities::Options(lsp_types::InlayHintOptions {
resolve_provider: Some(false),
..Default::default()
}),
));
}
}
#[cfg(test)]
mod tests {
use super::inlay_hint_of;
use cpp_code_analysis::{
CompilerConfig, MemoryFiles, OpenDocuments, ParameterHint, Session, SessionFiles,
WatchFilter,
};
fn a_held_file(text: &str) -> (Session<MemoryFiles>, std::path::PathBuf) {
let files = MemoryFiles::new().with_file("/p/a.cpp", text);
let providers = SessionFiles::new(OpenDocuments::new(), files);
let mut session = Session::with_config(
"/p",
providers,
WatchFilter::new("/p"),
CompilerConfig::default(),
);
session.load("/p/a.cpp");
(session, std::path::PathBuf::from("/p/a.cpp"))
}
#[test]
fn a_parameter_hint_becomes_a_labelled_hint_at_the_argument() {
let text = "int x = scale(3);\n";
let (session, path) = a_held_file(text);
let file = session.files().held(&path).expect("the file is held");
let hint = inlay_hint_of(
file,
ParameterHint {
offset: text.find('3').expect("the fixture"),
name: "count".to_string(),
},
)
.expect("the offset is in the file");
assert_eq!(hint.position.line, 0);
assert_eq!(hint.position.character, 14, "the hint sits on the argument");
match &hint.label {
lsp_types::InlayHintLabel::String(text) => assert_eq!(text, "count:"),
other => panic!("a plain label was expected, not label parts: {other:?}"),
}
assert_eq!(hint.kind, Some(lsp_types::InlayHintKind::PARAMETER));
assert_eq!(
hint.padding_right,
Some(true),
"the space goes after the colon"
);
assert!(
inlay_hint_of(
file,
ParameterHint {
offset: text.len() + 10,
name: "count".to_string(),
}
)
.is_none()
);
}
}