use lsp_types::{
ClientCapabilities, SelectionRange, SelectionRangeParams, SelectionRangeProviderCapability,
ServerCapabilities,
};
use tokio_util::sync::CancellationToken;
use super::RegisterCapabilities;
use crate::context::{RequestOutcome, ServerContextSnapshot, snapshot_query};
use crate::util::{file_offset_at_position, position_in_file, uri_to_file_path};
pub async fn on_selection_range(
context: ServerContextSnapshot,
params: SelectionRangeParams,
cancel_token: CancellationToken,
) -> RequestOutcome<Vec<SelectionRange>> {
let uri = params.text_document.uri;
let positions = params.positions;
if let Some(path) = uri_to_file_path(&uri) {
context.analysis().prepare(&path).await;
}
snapshot_query(context.analysis(), cancel_token, move |session| {
let path = uri_to_file_path(&uri)?;
let view = session.view_of_the_file(&path)?;
let held = session.files().held(&view.path)?;
let mut answered = Vec::with_capacity(positions.len());
for position in positions {
let offset = file_offset_at_position(&view, position)?;
let chain = session.selection_chain(&view, offset);
answered.push(chain_of(held, &chain)?);
}
Some(answered)
})
.await
}
pub fn chain_of(
file: &cpp_code_analysis::VfsFile,
chain: &[cpp_parser::SourceRange],
) -> Option<SelectionRange> {
let mut outer: Option<SelectionRange> = None;
for range in chain.iter().rev() {
let start = position_in_file(file, range.start_offset)?;
let end = position_in_file(file, range.end_offset())?;
outer = Some(SelectionRange {
range: lsp_types::Range::new(start, end),
parent: outer.map(Box::new),
});
}
outer
}
pub struct SelectionRangeCapabilities;
impl RegisterCapabilities for SelectionRangeCapabilities {
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
server_capabilities.selection_range_provider =
Some(SelectionRangeProviderCapability::Simple(true));
}
}
#[cfg(test)]
mod tests {
use super::chain_of;
use cpp_code_analysis::{
CompilerConfig, MemoryFiles, OpenDocuments, Session, SessionFiles, WatchFilter,
};
use lsp_types::SelectionRange;
fn rungs(range: &SelectionRange) -> Vec<(u32, u32)> {
let mut found = vec![(range.range.start.line, range.range.end.line)];
let mut current = range.parent.as_deref();
while let Some(parent) = current {
found.push((parent.range.start.line, parent.range.end.line));
current = parent.parent.as_deref();
}
found
}
#[test]
fn a_chain_becomes_nested_ranges() {
let source = "int f() {\n return 1;\n}\n";
let files = MemoryFiles::new().with_file("/p/a.cpp", source);
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");
let held = session.files().held("/p/a.cpp").expect("the file is held");
let view =
cpp_code_analysis::FileView::parse_with(held, &cpp_parser::MacroEnvironment::default());
let file = session.files().held("/p/a.cpp").expect("held");
let chain = view.selection_chain(cpp_code_analysis::file::coordinates::ReadingOffset::new(
source.find('1').expect("the fixture has one"),
));
assert!(chain.len() >= 3, "the fixture has several rungs: {chain:?}");
let nested = chain_of(file, &chain).expect("every rung is a place in the file");
assert_eq!(
rungs(&nested),
vec![(1, 1), (1, 1), (0, 3), (0, 3)],
"the token, the statement, the function, and the file: {nested:?}"
);
let mut current = Some(&nested);
while let Some(range) = current {
if let Some(parent) = range.parent.as_deref() {
assert!(
parent.range.start <= range.range.start && range.range.end <= parent.range.end,
"a parent has to contain its child: {range:?} inside {parent:?}"
);
}
current = range.parent.as_deref();
}
assert_eq!(chain_of(file, &[]), None);
}
}