use std::collections::BTreeSet;
use lsp_types::{
CompletionOptions, ExecuteCommandOptions, HoverProviderCapability, ServerCapabilities,
};
use crate::error::BuildError;
#[derive(Default)]
pub(crate) struct CapabilityBuilder {
caps: ServerCapabilities,
commands: BTreeSet<String>,
}
impl CapabilityBuilder {
pub(crate) fn set_hover(&mut self) -> Result<(), BuildError> {
self.caps.hover_provider = Some(HoverProviderCapability::Simple(true));
Ok(())
}
pub(crate) fn set_completion(&mut self, options: CompletionOptions) -> Result<(), BuildError> {
match &self.caps.completion_provider {
Some(existing) if *existing != options => Err(BuildError::ConflictingCapability {
field: "completionProvider",
}),
_ => {
self.caps.completion_provider = Some(options);
Ok(())
}
}
}
pub(crate) fn add_command(&mut self, name: String) {
self.commands.insert(name);
}
pub(crate) fn finish(mut self) -> ServerCapabilities {
if !self.commands.is_empty() {
self.caps.execute_command_provider = Some(ExecuteCommandOptions {
commands: self.commands.into_iter().collect(),
work_done_progress_options: Default::default(),
});
}
self.caps
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hover_sets_only_hover_provider() {
let mut caps = CapabilityBuilder::default();
caps.set_hover().unwrap();
let caps = caps.finish();
assert_eq!(
caps.hover_provider,
Some(HoverProviderCapability::Simple(true))
);
assert_eq!(caps.completion_provider, None);
assert_eq!(caps.execute_command_provider, None);
}
#[test]
fn completion_advertises_supplied_options() {
let options = CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
};
let mut caps = CapabilityBuilder::default();
caps.set_completion(options.clone()).unwrap();
assert_eq!(caps.finish().completion_provider, Some(options));
}
#[test]
fn identical_completion_contributions_merge() {
let options = CompletionOptions::default();
let mut caps = CapabilityBuilder::default();
caps.set_completion(options.clone()).unwrap();
caps.set_completion(options.clone())
.expect("identical options merge without conflict");
assert_eq!(caps.finish().completion_provider, Some(options));
}
#[test]
fn disagreeing_completion_contributions_conflict() {
let mut caps = CapabilityBuilder::default();
caps.set_completion(CompletionOptions::default()).unwrap();
let err = caps
.set_completion(CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
})
.expect_err("differing completion options must not last-write-win");
assert_eq!(
err,
BuildError::ConflictingCapability {
field: "completionProvider"
}
);
}
#[test]
fn commands_merge_into_one_sorted_deduplicated_list() {
let mut caps = CapabilityBuilder::default();
for name in ["b.cmd", "a.cmd", "b.cmd"] {
caps.add_command(name.to_string());
}
let provider = caps
.finish()
.execute_command_provider
.expect("registered commands contribute an execute-command capability");
assert_eq!(
provider.commands,
vec!["a.cmd".to_string(), "b.cmd".to_string()],
"commands are sorted and de-duplicated, so the list is order-independent"
);
}
#[test]
fn no_contributions_yield_default_capabilities() {
assert_eq!(
CapabilityBuilder::default().finish(),
ServerCapabilities::default()
);
}
}