use lsp_types::{
CompletionOptions, ExecuteCommandOptions, HoverProviderCapability, ServerCapabilities,
};
use crate::error::BuildError;
#[derive(Default)]
pub(crate) struct CapabilityBuilder {
caps: ServerCapabilities,
commands: Vec<String>,
completion: CompletionFamily,
}
#[derive(Default)]
struct CompletionFamily {
options: Option<CompletionOptions>,
resolve: bool,
}
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.completion.options {
Some(existing) if *existing != options => Err(BuildError::ConflictingCapability {
field: "completionProvider",
}),
_ => {
self.completion.options = Some(options);
Ok(())
}
}
}
pub(crate) fn set_completion_resolve(&mut self) {
self.completion.resolve = true;
}
pub(crate) fn validate(&self) -> Result<(), BuildError> {
let clash = || BuildError::ConflictingCapability {
field: "completionProvider",
};
match &self.completion.options {
None if self.completion.resolve => Err(clash()),
Some(options) if self.completion.resolve && options.resolve_provider == Some(false) => {
Err(clash())
}
_ => Ok(()),
}
}
pub(crate) fn add_command(&mut self, name: String) {
if !self.commands.contains(&name) {
self.commands.push(name);
}
}
pub(crate) fn finish(mut self) -> ServerCapabilities {
if let Some(mut options) = self.completion.options {
if self.completion.resolve {
options.resolve_provider = Some(true);
}
self.caps.completion_provider = Some(options);
}
if !self.commands.is_empty() {
self.caps.execute_command_provider = Some(ExecuteCommandOptions {
commands: self.commands,
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 completion_resolve_sets_resolve_provider_on_the_base_capability() {
let options = CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
};
let mut caps = CapabilityBuilder::default();
caps.set_completion(options).unwrap();
caps.set_completion_resolve();
let merged = caps
.finish()
.completion_provider
.expect("completion contributes a completionProvider capability");
assert_eq!(
merged.resolve_provider,
Some(true),
"the resolve contribution augments the same capability, not a second one"
);
assert_eq!(
merged.trigger_characters,
Some(vec![".".to_string()]),
"the base feature's options survive the family merge"
);
}
#[test]
fn completion_resolve_merge_is_independent_of_registration_order() {
let options = || CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
};
let mut base_first = CapabilityBuilder::default();
base_first.set_completion(options()).unwrap();
base_first.set_completion_resolve();
let mut resolve_first = CapabilityBuilder::default();
resolve_first.set_completion_resolve();
resolve_first.set_completion(options()).unwrap();
assert_eq!(
base_first.finish().completion_provider,
resolve_first.finish().completion_provider,
"the merged capability does not depend on which feature registered first"
);
}
#[test]
fn completion_resolve_without_completion_fails_validation() {
let mut caps = CapabilityBuilder::default();
caps.set_completion_resolve();
let err = caps.validate().expect_err(
"resolve without its base feature must not advertise a dangling capability",
);
assert_eq!(
err,
BuildError::ConflictingCapability {
field: "completionProvider"
}
);
}
#[test]
fn a_base_that_denies_resolve_clashes_with_a_resolve_contribution() {
let mut caps = CapabilityBuilder::default();
caps.set_completion(CompletionOptions {
resolve_provider: Some(false),
..CompletionOptions::default()
})
.unwrap();
caps.set_completion_resolve();
let err = caps
.validate()
.expect_err("a base denying resolve and a resolve contribution must clash");
assert_eq!(
err,
BuildError::ConflictingCapability {
field: "completionProvider"
}
);
}
#[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_deduplicated_registration_order_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!["b.cmd".to_string(), "a.cmd".to_string()],
"commands are de-duplicated and keep registration order, not sorted order"
);
}
#[test]
fn no_contributions_yield_default_capabilities() {
assert_eq!(
CapabilityBuilder::default().finish(),
ServerCapabilities::default()
);
}
}