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

refactor(rome_lsp): refactor the initialization and configuration loading logic #4044

Merged
merged 2 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, who sends root_path? A client that still uses an old version of the LSP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the LSP specification, rootPath was deprecated in version 3.0 of the protocol in favor of rootUri. The rootUri field was later deprecated in version 3.16 in favor of workspaceFolders, but we don't support that yet

}

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>,
leops marked this conversation as resolved.
Show resolved Hide resolved

/// 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