use cpp_code_analysis::folding::FoldKind;
use lsp_types::{
ClientCapabilities, FoldingRange, FoldingRangeKind, FoldingRangeParams,
FoldingRangeProviderCapability, ServerCapabilities,
};
use tokio_util::sync::CancellationToken;
use super::RegisterCapabilities;
use crate::context::{RequestOutcome, ServerContextSnapshot, snapshot_query};
use crate::util::{position_in_file, uri_to_file_path};
pub async fn on_folding_range(
context: ServerContextSnapshot,
params: FoldingRangeParams,
cancel_token: CancellationToken,
) -> RequestOutcome<Vec<FoldingRange>> {
let uri = params.text_document.uri;
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)?;
Some(
session
.folding_ranges(&view)
.into_iter()
.filter_map(|fold| folding_range_of(held, fold))
.collect(),
)
})
.await
}
pub fn folding_range_of(
file: &cpp_code_analysis::VfsFile,
fold: cpp_code_analysis::folding::Fold,
) -> Option<FoldingRange> {
let start = position_in_file(file, fold.range.start_offset)?;
let end = position_in_file(file, fold.range.end_offset().saturating_sub(1))?;
if end.line <= start.line {
return None;
}
Some(FoldingRange {
start_line: start.line,
start_character: None,
end_line: end.line,
end_character: None,
kind: kind_of(fold.kind),
collapsed_text: None,
})
}
fn kind_of(kind: FoldKind) -> Option<FoldingRangeKind> {
match kind {
FoldKind::Code => None,
FoldKind::Comment => Some(FoldingRangeKind::Comment),
FoldKind::Region => Some(FoldingRangeKind::Region),
FoldKind::Imports => Some(FoldingRangeKind::Imports),
}
}
pub struct FoldingRangeCapabilities;
impl RegisterCapabilities for FoldingRangeCapabilities {
fn register_capabilities(server_capabilities: &mut ServerCapabilities, _: &ClientCapabilities) {
server_capabilities.folding_range_provider =
Some(FoldingRangeProviderCapability::Simple(true));
}
}
#[cfg(test)]
mod tests {
use super::folding_range_of;
use cpp_code_analysis::folding::{Fold, FoldKind};
use cpp_code_analysis::{
CompilerConfig, MemoryFiles, OpenDocuments, Session, SessionFiles, WatchFilter,
};
use std::path::PathBuf;
fn a_held_file(text: &str) -> (Session<MemoryFiles>, 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, PathBuf::from("/p/a.cpp"))
}
#[test]
fn a_fold_becomes_the_lines_it_hides() {
let text = "one\ntwo\nthree\nfour\n";
let (session, path) = a_held_file(text);
let file = session.files().held(&path).expect("the file is held");
let fold = Fold {
range: cpp_parser::SourceRange::new(4, text.len() - 5),
kind: FoldKind::Code,
};
let converted = folding_range_of(file, fold).expect("three lines are worth folding");
assert_eq!(converted.start_line, 1);
assert_eq!(
converted.end_line, 3,
"not 4, which would hide nothing of `four`"
);
let one_line = Fold {
range: cpp_parser::SourceRange::new(0, 3),
kind: FoldKind::Code,
};
assert_eq!(folding_range_of(file, one_line), None);
let empty = Fold {
range: cpp_parser::SourceRange::new(4, 0),
kind: FoldKind::Code,
};
assert_eq!(folding_range_of(file, empty), None);
}
}