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

Commit

Permalink
refactor(rome_lsp): refactor the initialization and configuration loa…
Browse files Browse the repository at this point in the history
…ding logic (#4044)

* refactor(rome_lsp): improve the initialization and configuration loading logic

* address PR review
  • Loading branch information
leops authored Dec 13, 2022
1 parent f349182 commit a090d60
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 105 deletions.
62 changes: 35 additions & 27 deletions crates/rome_lsp/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tokio::task::spawn_blocking;
use tower_lsp::jsonrpc::Result as LspResult;
use tower_lsp::{lsp_types::*, ClientSocket};
use tower_lsp::{LanguageServer, LspService, Server};
use tracing::{error, info, trace};
use tracing::{error, info, trace, warn};

pub struct LSPServer {
session: SessionHandle,
Expand Down Expand Up @@ -95,13 +95,11 @@ impl LSPServer {

entries.extend(workspace_entries);

if let Ok(client_information) = session.client_information.lock() {
if let Some(information) = client_information.as_ref() {
entries.push(RageEntry::pair("Client Name", &information.name));
if let Some(information) = session.client_information() {
entries.push(RageEntry::pair("Client Name", &information.name));

if let Some(version) = &information.version {
entries.push(RageEntry::pair("Client Version", version))
}
if let Some(version) = &information.version {
entries.push(RageEntry::pair("Client Version", version))
}
}
}
Expand Down Expand Up @@ -188,28 +186,39 @@ impl LSPServer {

#[tower_lsp::async_trait]
impl LanguageServer for LSPServer {
#[tracing::instrument(level = "debug", skip(self))]
// The `root_path` field is deprecated, but we still read it so we can print a warning about it
#[allow(deprecated)]
#[tracing::instrument(
level = "debug",
skip_all,
fields(
root_uri = params.root_uri.as_ref().map(display),
capabilities = debug(&params.capabilities),
client_info = params.client_info.as_ref().map(debug),
root_path = params.root_path,
workspace_folders = params.workspace_folders.as_ref().map(debug),
)
)]
async fn initialize(&self, params: InitializeParams) -> LspResult<InitializeResult> {
info!("Starting Rome Language Server...");
self.is_initialized.store(true, Ordering::Relaxed);

self.session
.client_capabilities
.write()
.unwrap()
.replace(params.capabilities);
self.session.initialize(
params.capabilities,
params.client_info.map(|client_info| ClientInformation {
name: client_info.name,
version: client_info.version,
}),
params.root_uri,
);

if let Some(uri) = params.root_uri {
self.session.root_uri.write().unwrap().replace(uri);
if params.root_path.is_some() {
warn!("The Rome Server was initialized with the deprecated `root_path` parameter: this is not supported, use `root_uri` instead");
}

if let Some(client_info) = params.client_info {
let mut client_information = self.session.client_information.lock().unwrap();
*client_information = Some(ClientInformation {
name: client_info.name,
version: client_info.version,
})
};
if params.workspace_folders.is_some() {
warn!("The Rome Server was initialized with the `workspace_folders` parameter: this is unsupported at the moment, use `root_uri` instead");
}

let init = InitializeResult {
capabilities: server_capabilities(),
Expand All @@ -228,8 +237,8 @@ impl LanguageServer for LSPServer {

info!("Attempting to load the configuration from 'rome.json' file");

self.session.update_configuration().await;
self.session.fetch_client_configuration().await;
self.session.load_client_configuration().await;
self.session.load_workspace_settings().await;

let msg = format!("Server initialized with PID: {}", std::process::id());
self.session
Expand All @@ -250,7 +259,7 @@ impl LanguageServer for LSPServer {
#[tracing::instrument(level = "debug", skip(self))]
async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
let _ = params;
self.session.fetch_client_configuration().await;
self.session.load_client_configuration().await;
self.setup_capabilities().await;
}

Expand All @@ -268,8 +277,7 @@ impl LanguageServer for LSPServer {
let possible_rome_json = file_path.strip_prefix(&base_path);
if let Ok(possible_rome_json) = possible_rome_json {
if possible_rome_json.display().to_string() == CONFIG_NAME {
self.session.update_configuration().await;
self.session.fetch_client_configuration().await;
self.session.load_workspace_settings().await;
self.session.update_all_diagnostics().await;
// for now we are only interested to the configuration file,
// so it's OK to exist the loop
Expand Down
164 changes: 86 additions & 78 deletions crates/rome_lsp/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ use rome_analyze::RuleCategories;
use rome_console::markup;
use rome_diagnostics::location::FileId;
use rome_fs::{FileSystem, OsFileSystem, RomePath};
use rome_service::configuration::Configuration;
use rome_service::workspace::{FeatureName, PullDiagnosticsParams, SupportsFeatureParams};
use rome_service::workspace::{RageEntry, RageParams, RageResult, UpdateSettingsParams};
use rome_service::{load_config, Workspace};
use rome_service::{DynRef, RomeError};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::{Arc, Mutex};
use tokio::sync::Notify;
use tokio::sync::OnceCell;
use tower_lsp::lsp_types;
use tower_lsp::lsp_types::Url;
use tracing::{error, info, trace};
use tracing::{error, info};

pub(crate) struct ClientInformation {
/// The name of the client
Expand All @@ -42,10 +42,9 @@ pub(crate) struct Session {

/// The LSP client for this session.
pub(crate) client: tower_lsp::Client,
/// The capabilities provided by the client as part of [`lsp_types::InitializeParams`]
pub(crate) client_capabilities: RwLock<Option<lsp_types::ClientCapabilities>>,

pub(crate) client_information: Mutex<Option<ClientInformation>>,
/// The parameters provided by the client in the "initialize" request
initialize_params: OnceCell<InitializeParams>,

/// the configuration of the LSP
pub(crate) config: RwLock<Config>,
Expand All @@ -55,17 +54,20 @@ pub(crate) struct Session {
/// File system to read files inside the workspace
pub(crate) fs: DynRef<'static, dyn FileSystem>,

/// The configuration coming from `rome.json` file
pub(crate) configuration: RwLock<Option<Configuration>>,

pub(crate) root_uri: RwLock<Option<Url>>,

documents: RwLock<HashMap<lsp_types::Url, Document>>,
url_interner: RwLock<UrlInterner>,

pub(crate) cancellation: Arc<Notify>,
}

/// The parameters provided by the client in the "initialize" request
struct InitializeParams {
/// The capabilities provided by the client as part of [`lsp_types::InitializeParams`]
client_capabilities: lsp_types::ClientCapabilities,
client_information: Option<ClientInformation>,
root_uri: Option<Url>,
}

pub(crate) type SessionHandle = Arc<Session>;

impl Session {
Expand All @@ -75,28 +77,40 @@ impl Session {
workspace: Arc<dyn Workspace>,
cancellation: Arc<Notify>,
) -> Self {
let client_capabilities = RwLock::new(Default::default());
let documents = Default::default();
let url_interner = Default::default();
let config = RwLock::new(Config::new());
let configuration = RwLock::new(None);
let root_uri = RwLock::new(None);
Self {
key,
client,
client_information: Default::default(),
client_capabilities,
initialize_params: OnceCell::default(),
workspace,
documents,
url_interner,
config,
fs: DynRef::Owned(Box::new(OsFileSystem)),
configuration,
root_uri,
cancellation,
}
}

/// Initialize this session instance with the incoming initialization parameters from the client
pub(crate) fn initialize(
&self,
client_capabilities: lsp_types::ClientCapabilities,
client_information: Option<ClientInformation>,
root_uri: Option<Url>,
) {
let result = self.initialize_params.set(InitializeParams {
client_capabilities,
client_information,
root_uri,
});

if let Err(err) = result {
error!("Failed to initialize session: {err}");
}
}

/// Get a [`Document`] matching the provided [`lsp_types::Url`]
///
/// If document does not exist, result is [SessionError::DocumentNotFound]
Expand Down Expand Up @@ -204,95 +218,89 @@ impl Session {

/// True if the client supports dynamic registration of "workspace/didChangeConfiguration" requests
pub(crate) fn can_register_did_change_configuration(&self) -> bool {
self.client_capabilities
.read()
.unwrap()
.as_ref()
.and_then(|c| c.workspace.as_ref())
self.initialize_params
.get()
.and_then(|c| c.client_capabilities.workspace.as_ref())
.and_then(|c| c.did_change_configuration)
.and_then(|c| c.dynamic_registration)
== Some(true)
}

/// Returns the base path of the workspace on the filesystem if it has one
pub(crate) fn base_path(&self) -> Option<PathBuf> {
let root_uri = self.root_uri.read().unwrap();
root_uri.as_ref().and_then(|root_uri| match root_uri.to_file_path() {
let initialize_params = self.initialize_params.get()?;

let root_uri = initialize_params.root_uri.as_ref()?;
match root_uri.to_file_path() {
Ok(base_path) => Some(base_path),
Err(()) => {
error!("The Workspace root URI {root_uri:?} could not be parsed as a filesystem path");
error!(
"The Workspace root URI {root_uri:?} could not be parsed as a filesystem path"
);
None
}
})
}
}

/// Returns a reference to the client informations for this session
pub(crate) fn client_information(&self) -> Option<&ClientInformation> {
self.initialize_params.get()?.client_information.as_ref()
}

/// This function attempts to read the configuration from the root URI
pub(crate) async fn update_configuration(&self) {
/// This function attempts to read the `rome.json` configuration file from
/// the root URI and update the workspace settings accordingly
#[tracing::instrument(level = "debug", skip(self))]
pub(crate) async fn load_workspace_settings(&self) {
let base_path = self.base_path();

match load_config(&self.fs, base_path) {
Ok(Some(configuration)) => {
info!("Configuration found, and it is valid!");
self.configuration.write().unwrap().replace(configuration);
info!("Loaded workspace settings: {configuration:#?}");

let result = self
.workspace
.update_settings(UpdateSettingsParams { configuration });

if let Err(error) = result {
error!("Failed to set workspace settings: {}", error)
}
}
Ok(None) => {
// Ignore, load_config already logs an error in this case
}
Err(err) => {
error!("Couldn't load the configuration file, reason:\n {}", err);
error!("Couldn't load the workspace settings, reason:\n {}", err);
}
_ => {}
};
}
}

/// Requests "workspace/configuration" from client and updates Session config
pub(crate) async fn fetch_client_configuration(&self) {
#[tracing::instrument(level = "debug", skip(self))]
pub(crate) async fn load_client_configuration(&self) {
let item = lsp_types::ConfigurationItem {
scope_uri: None,
section: Some(String::from(CONFIGURATION_SECTION)),
};
let items = vec![item];
let client_configurations = self.client.configuration(items).await;

if let Ok(client_configurations) = client_configurations {
client_configurations
.into_iter()
.next()
.and_then(|client_configuration| {
let mut config = self.config.write().unwrap();

config
.set_workspace_settings(client_configuration)
.map_err(|err| {
error!("Cannot set workspace settings: {}", err);
})
.ok()?;
self.update_workspace_settings();

Some(())
});
} else {
trace!("Cannot read configuration from the client");
}
}
let client_configurations = match self.client.configuration(vec![item]).await {
Ok(client_configurations) => client_configurations,
Err(err) => {
error!("Couldn't read configuration from the client: {err}");
return;
}
};

/// If updates the [Workspace] settings with the new configuration that was
/// read from file.
#[tracing::instrument(level = "debug", skip(self))]
pub(crate) fn update_workspace_settings(&self) {
let mut configuration = self.configuration.write().unwrap();

// This operation is intended, we want to consume the configuration because once it's read
// from the LSP, it's not needed anymore
if let Some(configuration) = configuration.take() {
trace!(
"The LSP will now use the following configuration: \n {:?}",
&configuration
);

let result = self
.workspace
.update_settings(UpdateSettingsParams { configuration });

if let Err(error) = result {
error!("{:?}", &error)
let client_configuration = client_configurations.into_iter().next();

if let Some(client_configuration) = client_configuration {
info!("Loaded client configuration: {client_configuration:#?}");

let mut config = self.config.write().unwrap();
if let Err(err) = config.set_workspace_settings(client_configuration) {
error!("Couldn't set client configuration: {}", err);
}
} else {
info!("Client did not return any configuration");
}
}

Expand Down

0 comments on commit a090d60

Please sign in to comment.