use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use lsp_types::notification::Notification;
use lsp_types::request::Request;
use lsp_types::{InitializeParams, ServerCapabilities, ServerInfo};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;
use tokio_util::sync::CancellationToken;
use tracing::warn;
use crate::capability::CapabilityBuilder;
use crate::codec::erase_value;
use crate::context::Context;
use crate::error::{BuildError, LspError};
use crate::features::FeatureSpec;
use crate::service::{Layer, UserLayer};
const RESERVED_METHODS: &[&str] = &[
"initialize",
"shutdown",
"exit",
"initialized",
"$/cancelRequest",
];
const EXECUTE_COMMAND_METHOD: &str = "workspace/executeCommand";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ProtocolMutation {
Open,
Change,
Close,
WorkspaceFolders,
Configuration,
Trace,
}
impl ProtocolMutation {
const OPEN_METHOD: &'static str = "textDocument/didOpen";
const CHANGE_METHOD: &'static str = "textDocument/didChange";
const CLOSE_METHOD: &'static str = "textDocument/didClose";
pub(crate) fn from_method(method: &str) -> Option<Self> {
match method {
Self::OPEN_METHOD => Some(Self::Open),
Self::CHANGE_METHOD => Some(Self::Change),
Self::CLOSE_METHOD => Some(Self::Close),
"workspace/didChangeWorkspaceFolders" => Some(Self::WorkspaceFolders),
"workspace/didChangeConfiguration" => Some(Self::Configuration),
"$/setTrace" => Some(Self::Trace),
_ => None,
}
}
}
type HandlerFuture = Pin<Box<dyn Future<Output = Result<Value, LspError>> + Send>>;
type NotificationFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
pub(crate) type ErasedRequestHandler<S> =
Box<dyn Fn(Arc<S>, Context, Value, CancellationToken) -> HandlerFuture + Send + Sync>;
pub(crate) type ErasedNotificationHandler<S> =
Box<dyn Fn(Arc<S>, Context, Value) -> NotificationFuture + Send + Sync>;
pub(crate) type ErasedCommandHandler<S> =
Box<dyn Fn(Arc<S>, Context, Vec<Value>, CancellationToken) -> HandlerFuture + Send + Sync>;
pub(crate) type ConfigureInitialize<S> =
Box<dyn FnOnce(&InitializeParams, &mut InitializeRegistrar<S>) -> Result<(), LspError> + Send>;
type OnInitializeFuture =
Pin<Box<dyn Future<Output = Result<Option<ServerInfo>, LspError>> + Send>>;
pub(crate) type OnInitialize<S> = Box<
dyn Fn(Arc<S>, Context, InitializeParams, CancellationToken) -> OnInitializeFuture
+ Send
+ Sync,
>;
fn erase_request<S, R, H, Fut>(handler: H) -> ErasedRequestHandler<S>
where
S: Send + Sync + 'static,
R: Request,
H: Fn(Arc<S>, Context, R::Params, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<R::Result, LspError>> + Send + 'static,
{
let handler = Arc::new(handler);
Box::new(move |state, ctx, params, ct| {
let handler = Arc::clone(&handler);
Box::pin(async move {
let parsed: R::Params =
serde_json::from_value(params).map_err(LspError::invalid_params)?;
let result = handler(state, ctx, parsed, ct).await?;
erase_value(result)
})
})
}
pub(crate) struct Registrations<S> {
requests: HashMap<String, ErasedRequestHandler<S>>,
notifications: HashMap<String, ErasedNotificationHandler<S>>,
built_in_hooks: HashMap<String, ErasedNotificationHandler<S>>,
commands: HashMap<String, ErasedCommandHandler<S>>,
capabilities: CapabilityBuilder,
}
impl<S: Send + Sync + 'static> Registrations<S> {
fn new() -> Self {
Self {
requests: HashMap::new(),
notifications: HashMap::new(),
built_in_hooks: HashMap::new(),
commands: HashMap::new(),
capabilities: CapabilityBuilder::default(),
}
}
fn add_feature<F, H, Fut>(&mut self, spec: F, handler: H) -> Result<(), BuildError>
where
F: FeatureSpec,
H: Fn(Arc<S>, Context, <F::Marker as Request>::Params, CancellationToken) -> Fut
+ Send
+ Sync
+ 'static,
Fut: Future<Output = Result<<F::Marker as Request>::Result, LspError>> + Send + 'static,
{
let method = <F::Marker as Request>::METHOD.to_string();
let erased = erase_request::<S, F::Marker, H, Fut>(handler);
self.insert_request(method, erased)?;
spec.contribute(&mut self.capabilities)
}
fn add_request<R, H, Fut>(&mut self, handler: H) -> Result<(), BuildError>
where
R: Request,
H: Fn(Arc<S>, Context, R::Params, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<R::Result, LspError>> + Send + 'static,
{
let method = R::METHOD.to_string();
let erased = erase_request::<S, R, H, Fut>(handler);
self.insert_request(method, erased)
}
fn insert_request(
&mut self,
method: String,
erased: ErasedRequestHandler<S>,
) -> Result<(), BuildError> {
if RESERVED_METHODS.contains(&method.as_str()) {
return Err(BuildError::ReservedMethod(method));
}
if self.requests.insert(method.clone(), erased).is_some() {
return Err(BuildError::DuplicateMethod(method));
}
Ok(())
}
fn add_notification<N, H, Fut>(&mut self, handler: H) -> Result<(), BuildError>
where
N: Notification,
H: Fn(Arc<S>, Context, N::Params) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let method = N::METHOD.to_string();
let handler = Arc::new(handler);
let erased: ErasedNotificationHandler<S> = Box::new(move |state, ctx, params| {
let handler = Arc::clone(&handler);
Box::pin(async move {
let parsed: N::Params = match serde_json::from_value(params) {
Ok(parsed) => parsed,
Err(error) => {
warn!(
method = N::METHOD,
%error,
"dropping notification with malformed params"
);
return;
}
};
handler(state, ctx, parsed).await;
})
});
if RESERVED_METHODS.contains(&method.as_str()) {
return Err(BuildError::ReservedMethod(method));
}
let table = if ProtocolMutation::from_method(&method).is_some() {
&mut self.built_in_hooks
} else {
&mut self.notifications
};
if table.insert(method.clone(), erased).is_some() {
return Err(BuildError::DuplicateMethod(method));
}
Ok(())
}
fn add_command<Args, Output, H, Fut>(
&mut self,
name: String,
handler: H,
) -> Result<(), BuildError>
where
Args: DeserializeOwned + Send + 'static,
Output: Serialize + 'static,
H: Fn(Arc<S>, Context, Args, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Output, LspError>> + Send + 'static,
{
if name.is_empty() {
return Err(BuildError::EmptyCommandName);
}
let handler = Arc::new(handler);
let erased: ErasedCommandHandler<S> = Box::new(move |state, ctx, arguments, ct| {
let handler = Arc::clone(&handler);
Box::pin(async move {
let args: Args = serde_json::from_value(Value::Array(arguments))
.map_err(LspError::invalid_params)?;
let result = handler(state, ctx, args, ct).await?;
erase_value(result)
})
});
if self.commands.insert(name.clone(), erased).is_some() {
return Err(BuildError::DuplicateCommand(name));
}
self.capabilities.add_command(name);
Ok(())
}
fn validate(&self) -> Result<(), BuildError> {
self.capabilities.validate()?;
if !self.commands.is_empty() && self.requests.contains_key(EXECUTE_COMMAND_METHOD) {
return Err(BuildError::ExecuteCommandConflict);
}
Ok(())
}
pub(crate) fn freeze(self) -> Router<S> {
Router {
requests: self.requests,
notifications: self.notifications,
built_in_hooks: self.built_in_hooks,
commands: self.commands,
capabilities: self.capabilities.finish(),
}
}
}
pub(crate) struct Router<S> {
requests: HashMap<String, ErasedRequestHandler<S>>,
notifications: HashMap<String, ErasedNotificationHandler<S>>,
built_in_hooks: HashMap<String, ErasedNotificationHandler<S>>,
commands: HashMap<String, ErasedCommandHandler<S>>,
capabilities: ServerCapabilities,
}
impl<S> Router<S> {
pub(crate) fn request(&self, method: &str) -> Option<&ErasedRequestHandler<S>> {
self.requests.get(method)
}
pub(crate) fn notification(&self, method: &str) -> Option<&ErasedNotificationHandler<S>> {
self.notifications.get(method)
}
pub(crate) fn built_in_hook(&self, method: &str) -> Option<&ErasedNotificationHandler<S>> {
self.built_in_hooks.get(method)
}
pub(crate) fn command(&self, name: &str) -> Option<&ErasedCommandHandler<S>> {
self.commands.get(name)
}
pub(crate) fn has_commands(&self) -> bool {
!self.commands.is_empty()
}
pub(crate) fn capabilities(&self) -> ServerCapabilities {
self.capabilities.clone()
}
}
pub struct ServerBuilder<S> {
state: Arc<S>,
registrations: Registrations<S>,
configure_initialize: Option<ConfigureInitialize<S>>,
on_initialize: Option<OnInitialize<S>>,
layers: Vec<UserLayer<S>>,
concurrency_limit: usize,
error: Option<BuildError>,
}
impl<S: Send + Sync + 'static> ServerBuilder<S> {
fn new(state: S) -> Self {
Self {
state: Arc::new(state),
registrations: Registrations::new(),
configure_initialize: None,
on_initialize: None,
layers: Vec::new(),
concurrency_limit: crate::DEFAULT_CONCURRENCY_LIMIT,
error: None,
}
}
pub fn feature<F, H, Fut>(mut self, spec: F, handler: H) -> Self
where
F: FeatureSpec,
H: Fn(Arc<S>, Context, <F::Marker as Request>::Params, CancellationToken) -> Fut
+ Send
+ Sync
+ 'static,
Fut: Future<Output = Result<<F::Marker as Request>::Result, LspError>> + Send + 'static,
{
if let Err(err) = self.registrations.add_feature(spec, handler) {
self.record(err);
}
self
}
pub fn request<R, H, Fut>(mut self, handler: H) -> Self
where
R: Request,
H: Fn(Arc<S>, Context, R::Params, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<R::Result, LspError>> + Send + 'static,
{
if let Err(err) = self.registrations.add_request::<R, H, Fut>(handler) {
self.record(err);
}
self
}
pub fn notification<N, H, Fut>(mut self, handler: H) -> Self
where
N: Notification,
H: Fn(Arc<S>, Context, N::Params) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
if let Err(err) = self.registrations.add_notification::<N, H, Fut>(handler) {
self.record(err);
}
self
}
pub fn command<Args, Output, H, Fut>(mut self, name: impl Into<String>, handler: H) -> Self
where
Args: DeserializeOwned + Send + 'static,
Output: Serialize + 'static,
H: Fn(Arc<S>, Context, Args, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Output, LspError>> + Send + 'static,
{
if let Err(err) = self
.registrations
.add_command::<Args, Output, H, Fut>(name.into(), handler)
{
self.record(err);
}
self
}
pub fn configure_initialize<F>(mut self, callback: F) -> Self
where
F: FnOnce(&InitializeParams, &mut InitializeRegistrar<S>) -> Result<(), LspError>
+ Send
+ 'static,
{
if self.configure_initialize.is_some() {
self.record(BuildError::DuplicateConfigureInitialize);
} else {
self.configure_initialize = Some(Box::new(callback));
}
self
}
pub fn on_initialize<H, Fut>(mut self, hook: H) -> Self
where
H: Fn(Arc<S>, Context, InitializeParams, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Option<ServerInfo>, LspError>> + Send + 'static,
{
if self.on_initialize.is_some() {
self.record(BuildError::DuplicateLifecycleHook("on_initialize"));
} else {
self.on_initialize = Some(Box::new(move |state, ctx, params, ct| {
Box::pin(hook(state, ctx, params, ct))
}));
}
self
}
pub fn layer<L>(mut self, layer: L) -> Self
where
L: Layer<S>,
{
self.layers.push(Arc::new(layer));
self
}
pub fn concurrency_limit(mut self, limit: usize) -> Self {
if limit == 0 {
self.record(BuildError::InvalidConcurrencyLimit);
} else {
self.concurrency_limit = limit;
}
self
}
pub fn build(mut self) -> Result<Server<S>, BuildError> {
if let Err(err) = self.registrations.validate() {
self.record(err);
}
if let Some(error) = self.error {
return Err(error);
}
Ok(Server {
state: self.state,
registrations: self.registrations,
configure_initialize: self.configure_initialize,
on_initialize: self.on_initialize,
layers: self.layers,
concurrency_limit: self.concurrency_limit,
})
}
fn record(&mut self, error: BuildError) {
if self.error.is_none() {
self.error = Some(error);
}
}
}
pub struct InitializeRegistrar<S> {
registrations: Registrations<S>,
error: Option<BuildError>,
}
impl<S: Send + Sync + 'static> InitializeRegistrar<S> {
pub(crate) fn new(registrations: Registrations<S>) -> Self {
Self {
registrations,
error: None,
}
}
pub fn feature<F, H, Fut>(&mut self, spec: F, handler: H) -> &mut Self
where
F: FeatureSpec,
H: Fn(Arc<S>, Context, <F::Marker as Request>::Params, CancellationToken) -> Fut
+ Send
+ Sync
+ 'static,
Fut: Future<Output = Result<<F::Marker as Request>::Result, LspError>> + Send + 'static,
{
self.try_register(|r| r.add_feature(spec, handler))
}
pub fn request<R, H, Fut>(&mut self, handler: H) -> &mut Self
where
R: Request,
H: Fn(Arc<S>, Context, R::Params, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<R::Result, LspError>> + Send + 'static,
{
self.try_register(|r| r.add_request::<R, H, Fut>(handler))
}
pub fn notification<N, H, Fut>(&mut self, handler: H) -> &mut Self
where
N: Notification,
H: Fn(Arc<S>, Context, N::Params) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
self.try_register(|r| r.add_notification::<N, H, Fut>(handler))
}
pub fn command<Args, Output, H, Fut>(
&mut self,
name: impl Into<String>,
handler: H,
) -> &mut Self
where
Args: DeserializeOwned + Send + 'static,
Output: Serialize + 'static,
H: Fn(Arc<S>, Context, Args, CancellationToken) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<Output, LspError>> + Send + 'static,
{
self.try_register(|r| r.add_command::<Args, Output, H, Fut>(name.into(), handler))
}
fn try_register(
&mut self,
op: impl FnOnce(&mut Registrations<S>) -> Result<(), BuildError>,
) -> &mut Self {
if self.error.is_none()
&& let Err(err) = op(&mut self.registrations)
{
self.error = Some(err);
}
self
}
pub(crate) fn commit(self) -> Result<Registrations<S>, BuildError> {
if let Some(error) = self.error {
return Err(error);
}
self.registrations.validate()?;
Ok(self.registrations)
}
}
pub struct Server<S> {
pub(crate) state: Arc<S>,
pub(crate) registrations: Registrations<S>,
pub(crate) configure_initialize: Option<ConfigureInitialize<S>>,
pub(crate) on_initialize: Option<OnInitialize<S>>,
pub(crate) layers: Vec<UserLayer<S>>,
pub(crate) concurrency_limit: usize,
}
impl<S: Send + Sync + 'static> Server<S> {
pub fn builder(state: S) -> ServerBuilder<S> {
ServerBuilder::new(state)
}
#[cfg(test)]
pub(crate) fn into_router(self) -> Router<S> {
self.registrations.freeze()
}
pub async fn serve<T>(self, transport: T) -> crate::Result<crate::Outcome>
where
T: crate::Transport,
{
crate::engine::run(self, transport).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use lsp_types::request::{ExecuteCommand, HoverRequest, Shutdown};
use lsp_types::{CompletionOptions, HoverProviderCapability};
struct DummyState;
async fn ok_hover(
_state: Arc<DummyState>,
_ctx: Context,
_params: lsp_types::HoverParams,
_ct: CancellationToken,
) -> Result<Option<lsp_types::Hover>, LspError> {
Ok(None)
}
async fn ok_completion(
_state: Arc<DummyState>,
_ctx: Context,
_params: lsp_types::CompletionParams,
_ct: CancellationToken,
) -> Result<Option<lsp_types::CompletionResponse>, LspError> {
Ok(None)
}
async fn ok_resolve(
_state: Arc<DummyState>,
_ctx: Context,
item: lsp_types::CompletionItem,
_ct: CancellationToken,
) -> Result<lsp_types::CompletionItem, LspError> {
Ok(item)
}
async fn noop_command(
_state: Arc<DummyState>,
_ctx: Context,
_args: Vec<String>,
_ct: CancellationToken,
) -> Result<(), LspError> {
Ok(())
}
async fn noop_notification(_state: Arc<DummyState>, _ctx: Context, _params: ()) {}
#[test]
fn duplicate_request_method_is_a_build_error() {
let err = Server::builder(DummyState)
.request::<HoverRequest, _, _>(ok_hover)
.request::<HoverRequest, _, _>(ok_hover)
.build()
.err()
.expect("second registration for the same method must fail");
assert_eq!(
err,
BuildError::DuplicateMethod("textDocument/hover".to_string())
);
}
#[test]
fn registering_a_reserved_method_is_a_build_error() {
async fn shutdown_handler(
_state: Arc<DummyState>,
_ctx: Context,
_params: (),
_ct: CancellationToken,
) -> Result<(), LspError> {
Ok(())
}
let err = Server::builder(DummyState)
.request::<Shutdown, _, _>(shutdown_handler)
.build()
.err()
.expect("shutdown is framework-reserved");
assert_eq!(err, BuildError::ReservedMethod("shutdown".to_string()));
}
#[test]
fn a_single_registration_builds_and_advertises_no_extra_capabilities() {
let server = Server::builder(DummyState)
.request::<HoverRequest, _, _>(ok_hover)
.build()
.expect("a lone custom request builds");
let router = server.into_router();
assert!(router.request("textDocument/hover").is_some());
assert!(router.request("nope").is_none());
assert_eq!(
router.capabilities(),
ServerCapabilities::default(),
"custom requests must not contribute capabilities"
);
}
#[test]
fn a_reserved_notification_method_is_a_build_error() {
let err = Server::builder(DummyState)
.notification::<lsp_types::notification::Exit, _, _>(noop_notification)
.build()
.err()
.expect("exit is framework-reserved");
assert_eq!(err, BuildError::ReservedMethod("exit".to_string()));
}
#[test]
fn a_duplicate_notification_method_is_a_build_error() {
let err = Server::builder(DummyState)
.notification::<lsp_types::notification::DidChangeConfiguration, _, _>(
|_s, _c, _p: lsp_types::DidChangeConfigurationParams| async {},
)
.notification::<lsp_types::notification::DidChangeConfiguration, _, _>(
|_s, _c, _p: lsp_types::DidChangeConfigurationParams| async {},
)
.build()
.err()
.expect("a repeated notification method must fail");
assert_eq!(
err,
BuildError::DuplicateMethod("workspace/didChangeConfiguration".to_string())
);
}
#[test]
fn workspace_mutation_hooks_contribute_no_catalog_capabilities() {
let server = Server::builder(DummyState)
.notification::<lsp_types::notification::DidChangeConfiguration, _, _>(
|_s, _c, _p: lsp_types::DidChangeConfigurationParams| async {},
)
.build()
.expect("a lone notification builds");
let router = server.into_router();
assert!(
router
.built_in_hook("workspace/didChangeConfiguration")
.is_some()
);
assert!(
router
.notification("workspace/didChangeConfiguration")
.is_none()
);
assert_eq!(router.capabilities(), ServerCapabilities::default());
}
#[test]
fn a_document_sync_registration_records_a_hook_not_a_route() {
let server = Server::builder(DummyState)
.notification::<lsp_types::notification::DidOpenTextDocument, _, _>(
|_s, _c, _p: lsp_types::DidOpenTextDocumentParams| async {},
)
.notification::<lsp_types::notification::DidSaveTextDocument, _, _>(
|_s, _c, _p: lsp_types::DidSaveTextDocumentParams| async {},
)
.build()
.expect("one hook and one ordinary notification build");
let router = server.into_router();
assert!(
router.built_in_hook("textDocument/didOpen").is_some(),
"a built-in document notification records a post-mutation hook"
);
assert!(
router.notification("textDocument/didOpen").is_none(),
"the hook is not a Router route, so it cannot shadow the built-in"
);
assert!(router.notification("textDocument/didSave").is_some());
assert!(router.built_in_hook("textDocument/didSave").is_none());
}
#[test]
fn a_duplicate_document_hook_is_a_build_error() {
let err = Server::builder(DummyState)
.notification::<lsp_types::notification::DidChangeTextDocument, _, _>(
|_s, _c, _p: lsp_types::DidChangeTextDocumentParams| async {},
)
.notification::<lsp_types::notification::DidChangeTextDocument, _, _>(
|_s, _c, _p: lsp_types::DidChangeTextDocumentParams| async {},
)
.build()
.err()
.expect("a built-in notification takes at most one hook");
assert_eq!(
err,
BuildError::DuplicateMethod("textDocument/didChange".to_string())
);
}
#[test]
fn document_hooks_contribute_no_capabilities() {
let without_hook = Server::builder(DummyState)
.build()
.expect("an empty server builds")
.into_router()
.capabilities();
let with_hook = Server::builder(DummyState)
.notification::<lsp_types::notification::DidCloseTextDocument, _, _>(
|_s, _c, _p: lsp_types::DidCloseTextDocumentParams| async {},
)
.build()
.expect("a lone document hook builds")
.into_router()
.capabilities();
assert_eq!(
with_hook, without_hook,
"observing a built-in advertises nothing the built-in did not"
);
}
#[test]
fn an_empty_command_name_is_a_build_error() {
let err = Server::builder(DummyState)
.command::<Vec<String>, (), _, _>("", noop_command)
.build()
.err()
.expect("an empty command name must fail");
assert_eq!(err, BuildError::EmptyCommandName);
}
#[test]
fn a_duplicate_command_name_is_a_build_error() {
let err = Server::builder(DummyState)
.command::<Vec<String>, (), _, _>("my.cmd", noop_command)
.command::<Vec<String>, (), _, _>("my.cmd", noop_command)
.build()
.err()
.expect("a repeated command name must fail");
assert_eq!(err, BuildError::DuplicateCommand("my.cmd".to_string()));
}
#[test]
fn commands_alongside_an_explicit_execute_command_handler_conflict() {
async fn raw_execute(
_state: Arc<DummyState>,
_ctx: Context,
_params: lsp_types::ExecuteCommandParams,
_ct: CancellationToken,
) -> Result<Option<serde_json::Value>, LspError> {
Ok(None)
}
let err = Server::builder(DummyState)
.command::<Vec<String>, (), _, _>("my.cmd", noop_command)
.request::<ExecuteCommand, _, _>(raw_execute)
.build()
.err()
.expect("a command and a raw execute-command handler cannot coexist");
assert_eq!(err, BuildError::ExecuteCommandConflict);
}
#[test]
fn registered_commands_contribute_one_execute_command_capability() {
let server = Server::builder(DummyState)
.command::<Vec<String>, (), _, _>("b.cmd", noop_command)
.command::<Vec<String>, (), _, _>("a.cmd", noop_command)
.build()
.expect("commands build");
let provider = server
.into_router()
.capabilities()
.execute_command_provider
.expect("commands advertise an execute-command capability");
assert_eq!(
provider.commands,
vec!["b.cmd".to_string(), "a.cmd".to_string()],
"command names merge into one de-duplicated, registration-order list"
);
}
#[test]
fn hover_feature_sets_only_hover_provider() {
let server = Server::builder(DummyState)
.feature(crate::features::hover(), ok_hover)
.build()
.expect("hover builds");
let router = server.into_router();
let caps = router.capabilities();
assert_eq!(
caps.hover_provider,
Some(HoverProviderCapability::Simple(true))
);
assert_eq!(caps.completion_provider, None);
assert!(router.request("textDocument/hover").is_some());
}
#[test]
fn hover_and_completion_merge_independent_of_order() {
let options = CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
};
let hover_first = Server::builder(DummyState)
.feature(crate::features::hover(), ok_hover)
.feature(crate::features::completion(options.clone()), ok_completion)
.build()
.expect("hover then completion builds")
.into_router()
.capabilities();
let completion_first = Server::builder(DummyState)
.feature(crate::features::completion(options.clone()), ok_completion)
.feature(crate::features::hover(), ok_hover)
.build()
.expect("completion then hover builds")
.into_router()
.capabilities();
assert_eq!(
hover_first, completion_first,
"capability merge is independent of registration order"
);
assert_eq!(
hover_first.completion_provider,
Some(options),
"completion advertises the supplied options"
);
}
#[test]
fn a_duplicate_feature_is_a_build_error_not_last_write_wins() {
let err = Server::builder(DummyState)
.feature(crate::features::hover(), ok_hover)
.feature(crate::features::hover(), ok_hover)
.build()
.err()
.expect("registering hover twice must fail");
assert_eq!(
err,
BuildError::DuplicateMethod("textDocument/hover".to_string())
);
}
#[test]
fn completion_and_resolve_merge_into_one_capability_independent_of_order() {
let options = || CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..CompletionOptions::default()
};
let base_first = Server::builder(DummyState)
.feature(crate::features::completion(options()), ok_completion)
.feature(crate::features::completion_resolve(), ok_resolve)
.build()
.expect("completion then resolve builds")
.into_router();
let resolve_first = Server::builder(DummyState)
.feature(crate::features::completion_resolve(), ok_resolve)
.feature(crate::features::completion(options()), ok_completion)
.build()
.expect("resolve then completion builds")
.into_router();
assert_eq!(
base_first.capabilities(),
resolve_first.capabilities(),
"the family merge is independent of registration order"
);
let merged = base_first
.capabilities()
.completion_provider
.expect("the family emits one completionProvider capability");
assert_eq!(merged.resolve_provider, Some(true));
assert_eq!(merged.trigger_characters, Some(vec![".".to_string()]));
assert!(base_first.request("textDocument/completion").is_some());
assert!(base_first.request("completionItem/resolve").is_some());
}
#[test]
fn completion_resolve_without_completion_is_a_build_error() {
let err = Server::builder(DummyState)
.feature(crate::features::completion_resolve(), ok_resolve)
.build()
.err()
.expect("resolve without its base feature must fail");
assert_eq!(
err,
BuildError::ConflictingCapability {
field: "completionProvider"
}
);
}
#[test]
fn unequal_resolve_contributions_within_the_family_fail() {
let err = Server::builder(DummyState)
.feature(
crate::features::completion(CompletionOptions {
resolve_provider: Some(false),
..CompletionOptions::default()
}),
ok_completion,
)
.feature(crate::features::completion_resolve(), ok_resolve)
.build()
.err()
.expect("a base that denies resolve and a resolve registration clash");
assert_eq!(
err,
BuildError::ConflictingCapability {
field: "completionProvider"
},
"capability construction never resolves a clash by last-write-wins"
);
}
async fn noop_on_initialize(
_state: Arc<DummyState>,
_ctx: Context,
_params: lsp_types::InitializeParams,
_ct: CancellationToken,
) -> Result<Option<lsp_types::ServerInfo>, LspError> {
Ok(None)
}
#[test]
fn duplicate_configure_initialize_is_a_build_error() {
let err = Server::builder(DummyState)
.configure_initialize(|_params, _registrar| Ok(()))
.configure_initialize(|_params, _registrar| Ok(()))
.build()
.err()
.expect("supplying configure_initialize twice must fail");
assert_eq!(err, BuildError::DuplicateConfigureInitialize);
}
#[test]
fn duplicate_on_initialize_is_a_build_error() {
let err = Server::builder(DummyState)
.on_initialize(noop_on_initialize)
.on_initialize(noop_on_initialize)
.build()
.err()
.expect("supplying on_initialize twice must fail");
assert_eq!(err, BuildError::DuplicateLifecycleHook("on_initialize"));
}
}