use super::*;
pub(crate) type DynError = Box<dyn std::error::Error + Sync + Send>;
pub fn run() -> Result<(), DynError> {
let (connection, io_threads) = Connection::stdio();
let (id, params) = connection.initialize_start()?;
let editor_settings = params
.get("initializationOptions")
.map(EditorSettings::from_client_value)
.unwrap_or_default();
let workspace_roots = workspace_roots_from_params(¶ms);
let init_result = InitializeResult {
capabilities: server_capabilities(),
server_info: Some(ServerInfo {
name: "arity".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
}),
};
connection.initialize_finish(id, serde_json::to_value(init_result)?)?;
main_loop(connection, editor_settings, workspace_roots)?;
io_threads.join()?;
Ok(())
}
pub(crate) fn workspace_roots_from_params(params: &serde_json::Value) -> Vec<PathBuf> {
let from_uri = |s: &str| s.parse::<Uri>().ok().and_then(|u| uri::to_path(&u));
let mut roots: Vec<PathBuf> = params
.get("workspaceFolders")
.and_then(|v| v.as_array())
.into_iter()
.flatten()
.filter_map(|folder| folder.get("uri").and_then(|u| u.as_str()))
.filter_map(from_uri)
.collect();
if roots.is_empty()
&& let Some(path) = params
.get("rootUri")
.and_then(|u| u.as_str())
.and_then(from_uri)
{
roots.push(path);
}
roots
}
pub(crate) fn server_capabilities() -> ServerCapabilities {
ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
document_formatting_provider: Some(OneOf::Left(true)),
document_range_formatting_provider: Some(OneOf::Left(true)),
code_action_provider: Some(CodeActionProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![":".to_string()]),
resolve_provider: Some(true),
..Default::default()
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
signature_help_provider: Some(SignatureHelpOptions {
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
retrigger_characters: Some(vec![")".to_string()]),
work_done_progress_options: Default::default(),
}),
definition_provider: Some(OneOf::Left(true)),
references_provider: Some(OneOf::Left(true)),
document_highlight_provider: Some(OneOf::Left(true)),
document_symbol_provider: Some(OneOf::Left(true)),
workspace_symbol_provider: Some(OneOf::Left(true)),
folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensOptions(
SemanticTokensOptions {
legend: semantic_tokens_legend(),
range: Some(false),
full: Some(SemanticTokensFullOptions::Bool(true)),
work_done_progress_options: Default::default(),
},
)),
rename_provider: Some(OneOf::Right(RenameOptions {
prepare_provider: Some(true),
work_done_progress_options: Default::default(),
})),
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: None,
file_operations: Some(WorkspaceFileOperationsServerCapabilities {
will_rename: Some(r_file_rename_registration()),
did_rename: Some(r_file_rename_registration()),
..Default::default()
}),
}),
..Default::default()
}
}
pub(crate) fn r_file_rename_registration() -> FileOperationRegistrationOptions {
FileOperationRegistrationOptions {
filters: vec![FileOperationFilter {
scheme: Some("file".to_string()),
pattern: FileOperationPattern {
glob: "**/*.{R,r}".to_string(),
matches: None,
options: None,
},
}],
}
}
pub(crate) fn main_loop(
connection: Connection,
editor_settings: EditorSettings,
workspace_roots: Vec<PathBuf>,
) -> Result<(), DynError> {
let (out_tx, out_rx) = crossbeam_channel::unbounded::<Outbound>();
let (lint_tx, lint_rx) = crossbeam_channel::unbounded::<LintMsg>();
let (read_tx, read_rx) = crossbeam_channel::unbounded::<ReadJob>();
let read_pool = TaskPool::new("arity-lsp-read", read_pool_size());
let lint_handle = spawn_lint_thread(lint_rx, read_rx, out_tx, read_pool.spawner());
if !workspace_roots.is_empty() {
let _ = lint_tx.send(LintMsg::SeedWorkspace {
roots: workspace_roots,
});
}
let mut state = GlobalState::new(
connection.sender.clone(),
lint_tx,
read_tx,
read_pool.spawner(),
editor_settings,
);
loop {
select! {
recv(connection.receiver) -> msg => {
let Ok(msg) = msg else { break };
match msg {
Message::Request(req) => {
if connection.handle_shutdown(&req)? {
break;
}
state.on_request(req);
}
Message::Notification(not) => state.on_notification(not),
Message::Response(_) => {}
}
}
recv(out_rx) -> ob => {
let Ok(ob) = ob else { break };
state.on_outbound(ob);
}
}
}
drop(state); let _ = lint_handle.join();
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn workspace_roots_parses_folders_then_root_uri() {
let uri = test_uri();
let want = vec![test_path().to_path_buf()];
let params = serde_json::json!({
"workspaceFolders": [{ "uri": uri.as_str(), "name": "w" }],
});
assert_eq!(workspace_roots_from_params(¶ms), want);
let params = serde_json::json!({ "rootUri": uri.as_str() });
assert_eq!(workspace_roots_from_params(¶ms), want);
assert!(workspace_roots_from_params(&serde_json::json!({})).is_empty());
}
}