Skip to content
This repository has been archived by the owner on Oct 24, 2023. It is now read-only.

Commit

Permalink
Merge pull request #24 from jokeyrhyme/workspace-did-change-configura…
Browse files Browse the repository at this point in the history
…tion

feat: workspace/didChangeConfiguration
  • Loading branch information
jokeyrhyme authored Oct 10, 2023
2 parents 8f6cf04 + 1ee7922 commit 36fabfd
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ Language Server Protocol implementation for nushell
and [textDocument/didOpen](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_didOpen)
- [x] [textDocument/publishDiagnostics](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics) -> `nu --ide-check`
- [x] [workspace/configuration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration)
- [x] [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeConfiguration)
- [ ] [textDocument/inlayHint](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_inlayHint) -> `nu --ide-check`
- [ ] [workspace/didChangeConfiguration](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeConfiguration)
- [ ] raise a PR for `vscode-nushell-lang` to replace its wrapper/glue code with `nuls`

### stretch goals
Expand Down
42 changes: 40 additions & 2 deletions src/backend/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ use crate::{
nu::{run_compiler, IdeComplete, IdeGotoDef, IdeHover},
};

use tower_lsp::jsonrpc::Result;
#[allow(clippy::wildcard_imports)]
use tower_lsp::lsp_types::*;
use tower_lsp::LanguageServer;
use tower_lsp::{jsonrpc::Result, lsp_types::notification::DidChangeConfiguration};
use tower_lsp::{lsp_types::notification::Notification, LanguageServer};

#[tower_lsp::async_trait]
impl LanguageServer for Backend {
Expand All @@ -27,6 +27,14 @@ impl LanguageServer for Backend {
};
}

async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
if let Err(e) = self.try_did_change_configuration(params).await {
self.client
.log_message(MessageType::ERROR, format!("{e:?}"))
.await;
}
}

async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
self.client
.log_message(
Expand Down Expand Up @@ -65,6 +73,16 @@ impl LanguageServer for Backend {
// panic: this is the only place we `OnceLock::set`,
// so we've entered strange territory if something else writes to them first

self.can_change_configuration
.set(matches!(
params.capabilities.workspace,
Some(WorkspaceClientCapabilities {
did_change_configuration: Some(_),
..
})
))
.expect("server value initialized out of sequence");

self.can_lookup_configuration
.set(matches!(
params.capabilities.workspace,
Expand Down Expand Up @@ -116,6 +134,26 @@ impl LanguageServer for Backend {
}

async fn initialized(&self, _params: InitializedParams) {
if *self.can_change_configuration.get().unwrap_or(&false) {
let method = String::from(DidChangeConfiguration::METHOD);
if let Err(e) = self
.client
.register_capability(vec![Registration {
id: method.clone(),
method,
register_options: None,
}])
.await
{
self.client
.log_message(
MessageType::INFO,
format!("unable to register capability: {e:?}"),
)
.await;
};
}

self.client
.log_message(MessageType::INFO, "server initialized!")
.await;
Expand Down
111 changes: 100 additions & 11 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::OnceLock;
use std::time::{Duration, Instant};
use std::{ffi::OsStr, sync::RwLock};
Expand All @@ -9,6 +10,7 @@ use crate::{
};
use lsp_textdocument::{FullTextDocument, TextDocuments};

use serde::Deserialize;
use tower_lsp::lsp_types::notification::{
DidChangeTextDocument, DidCloseTextDocument, Notification,
};
Expand All @@ -18,10 +20,13 @@ use tower_lsp::Client;
use tower_lsp::{jsonrpc::Result, lsp_types::notification::DidOpenTextDocument};

pub(crate) struct Backend {
can_change_configuration: OnceLock<bool>,
can_lookup_configuration: OnceLock<bool>,
can_publish_diagnostics: OnceLock<bool>,
client: Client,
documents: RwLock<TextDocuments>,
document_settings: RwLock<HashMap<Url, IdeSettings>>,
global_settings: RwLock<IdeSettings>,
last_validated: RwLock<Instant>,
}

Expand All @@ -42,29 +47,73 @@ impl Backend {
}

async fn get_document_settings(&self, uri: &Url) -> Result<IdeSettings> {
if *self.can_lookup_configuration.get().unwrap_or(&false) {
let values = self
.client
.configuration(vec![ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(String::from("nushellLanguageServer")),
}])
.await?;

if let Some(value) = values.into_iter().next() {
return Ok(serde_json::from_value::<IdeSettings>(value).unwrap_or_default());
if !self.can_lookup_configuration.get().unwrap_or(&false) {
self.client
.log_message(
MessageType::INFO,
"no per-document settings lookup capability, returning global settings ...",
)
.await;
let global_settings = self.global_settings.read().map_err(|e| {
tower_lsp::jsonrpc::Error::invalid_params(format!(
"cannot read global settings: {e:?}"
))
})?;
return Ok(global_settings.clone());
}

{
self.client
.log_message(
MessageType::INFO,
"checking per-document settings cache ...",
)
.await;
let document_settings = self.document_settings.read().map_err(|e| {
map_err_to_internal_error(&e, format!("cannot read per-document settings: {e:?}"))
})?;
if let Some(settings) = document_settings.get(uri) {
return Ok(settings.clone());
}
}

self.client
.log_message(
MessageType::INFO,
"fetching per-document settings for cache ...",
)
.await;
let values = self
.client
.configuration(vec![ConfigurationItem {
scope_uri: Some(uri.clone()),
section: Some(String::from("nushellLanguageServer")),
}])
.await?;
if let Some(value) = values.into_iter().next() {
let settings: IdeSettings = serde_json::from_value(value).unwrap_or_default();
let mut document_settings = self.document_settings.write().map_err(|e| {
map_err_to_internal_error(&e, format!("cannot write per-document settings: {e:?}"))
})?;
document_settings.insert(uri.clone(), settings.clone());
return Ok(settings);
}

self.client
.log_message(MessageType::INFO, "fallback, returning default settings")
.await;
Ok(IdeSettings::default())
}

pub fn new(client: Client) -> Self {
Self {
can_change_configuration: OnceLock::new(),
can_lookup_configuration: OnceLock::new(),
can_publish_diagnostics: OnceLock::new(),
client,
documents: RwLock::new(TextDocuments::new()),
document_settings: RwLock::new(HashMap::new()),
global_settings: RwLock::new(IdeSettings::default()),
last_validated: RwLock::new(Instant::now()),
}
}
Expand Down Expand Up @@ -103,6 +152,40 @@ impl Backend {
Ok(())
}

async fn try_did_change_configuration(
&self,
params: DidChangeConfigurationParams,
) -> Result<()> {
if *self.can_lookup_configuration.get().unwrap_or(&false) {
let mut document_settings = self.document_settings.write().map_err(|e| {
map_err_to_internal_error(&e, format!("cannot write per-document settings: {e:?}"))
})?;
document_settings.clear();
} else {
let settings: ClientSettingsPayload =
serde_json::from_value(params.settings).unwrap_or_default();
let mut global_settings = self.global_settings.write().map_err(|e| {
map_err_to_internal_error(&e, format!("cannot write global settings: {e:?}"))
})?;
*global_settings = settings.nushell_language_server;
}

// Revalidate all open text documents
let uris: Vec<Url> = {
let documents = self.documents.read().map_err(|e| {
tower_lsp::jsonrpc::Error::invalid_params(format!(
"cannot read from document cache: {e:?}"
))
})?;
documents.documents().keys().cloned().collect()
};
for uri in uris {
self.validate_document(&uri).await?;
}

Ok(())
}

fn try_did_close(&self, params: DidCloseTextDocumentParams) -> Result<()> {
let mut documents = self.documents.write().map_err(|e| {
map_err_to_internal_error(&e, format!("cannot write to document cache: {e:?}"))
Expand Down Expand Up @@ -174,3 +257,9 @@ impl Backend {
Ok(())
}
}

#[derive(Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
struct ClientSettingsPayload {
nushell_language_server: IdeSettings,
}

0 comments on commit 36fabfd

Please sign in to comment.