Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Centralize server-level config in new 'ServerConfig' struct. #48

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
64 changes: 14 additions & 50 deletions server/src/configuration_set.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
// Copyright (c) ZeroC, Inc.

use crate::slice_config;
use crate::slice_config::{compute_slice_options, ServerConfig, SliceConfig};
use crate::utils::sanitize_path;
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use slice_config::SliceConfig;
use slicec::slice_options::SliceOptions;
use slicec::{ast::Ast, diagnostics::Diagnostic, slice_file::SliceFile};
use slicec::compilation_state::CompilationState;

#[derive(Debug)]
#[derive(Debug, Default)]
pub struct CompilationData {
pub ast: Ast,
pub files: HashMap<String, SliceFile>,
}

impl Default for CompilationData {
fn default() -> Self {
Self {
ast: Ast::create(),
files: HashMap::default(),
}
}
}

// Necessary for using `CompilationData` within async functions.
//
// # Safety
Expand All @@ -33,7 +22,7 @@ impl Default for CompilationData {
unsafe impl Send for CompilationData {}
unsafe impl Sync for CompilationData {}

#[derive(Debug)]
#[derive(Debug, Default)]
pub struct ConfigurationSet {
pub slice_config: SliceConfig,
pub compilation_data: CompilationData,
Expand All @@ -42,53 +31,28 @@ pub struct ConfigurationSet {
}

impl ConfigurationSet {
/// Creates a new `ConfigurationSet` using the given root and built-in-slice paths.
pub fn new(root_path: PathBuf, built_in_path: String) -> Self {
let slice_config = SliceConfig {
paths: vec![],
workspace_root_path: Some(root_path),
built_in_slice_path: Some(built_in_path),
};
let compilation_data = CompilationData::default();
Self { slice_config, compilation_data, cached_slice_options: None }
}

/// Parses a vector of `ConfigurationSet` from a JSON array, root path, and built-in path.
pub fn parse_configuration_sets(
config_array: &[serde_json::Value],
root_path: &Path,
built_in_path: &str,
) -> Vec<ConfigurationSet> {
/// Parses a vector of `ConfigurationSet` from a JSON array.
pub fn parse_configuration_sets(config_array: &[serde_json::Value]) -> Vec<Self> {
config_array
.iter()
.map(|value| ConfigurationSet::from_json(value, root_path, built_in_path))
.map(ConfigurationSet::from_json)
.collect::<Vec<_>>()
}

/// Constructs a `ConfigurationSet` from a JSON value.
fn from_json(value: &serde_json::Value, root_path: &Path, built_in_path: &str) -> Self {
// Parse the paths and `include_built_in_types` from the configuration set
let paths = parse_paths(value);
let include_built_in = parse_include_built_in(value);

// Create the SliceConfig and CompilationState
fn from_json(value: &serde_json::Value) -> Self {
let slice_config = SliceConfig {
paths,
workspace_root_path: Some(root_path.to_owned()),
built_in_slice_path: include_built_in.then(|| built_in_path.to_owned()),
slice_search_paths: parse_paths(value),
include_built_in_slice_files: parse_include_built_in(value),
};
let compilation_data = CompilationData::default();
Self { slice_config, compilation_data, cached_slice_options: None }
Self { slice_config, ..Self::default() }
}

pub fn trigger_compilation(&mut self) -> Vec<Diagnostic> {
pub fn trigger_compilation(&mut self, server_config: &ServerConfig) -> Vec<Diagnostic> {
// Re-compute the `slice_options` we're going to pass into the compiler, if necessary.
if self.cached_slice_options.is_none() {
let mut slice_options = SliceOptions::default();
slice_options.references = self.slice_config.resolve_paths();
self.cached_slice_options = Some(slice_options);
}
let slice_options = self.cached_slice_options.as_ref().unwrap();
let slice_options = self.cached_slice_options.get_or_insert_with(|| {
compute_slice_options(server_config, &self.slice_config)
});

// Perform the compilation.
let compilation_state = slicec::compile_from_options(slice_options, |_| {}, |_| {});
Expand Down
11 changes: 5 additions & 6 deletions server/src/diagnostic_ext.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) ZeroC, Inc.

use crate::configuration_set::ConfigurationSet;
use crate::session::Session;
use crate::utils::convert_slice_url_to_uri;
use crate::{notifications, show_popup};

Expand Down Expand Up @@ -48,11 +49,9 @@ pub async fn publish_diagnostics_for_set(

/// Triggers and compilation and publishes any diagnostics that are reported.
/// It does this for all configuration sets.
pub async fn compile_and_publish_diagnostics(
client: &Client,
configuration_sets: &Mutex<Vec<ConfigurationSet>>,
) {
let mut configuration_sets = configuration_sets.lock().await;
pub async fn compile_and_publish_diagnostics(client: &Client, session: &Session) {
let mut configuration_sets = session.configuration_sets.lock().await;
let server_config = session.server_config.read().await;

client
.log_message(
Expand All @@ -62,7 +61,7 @@ pub async fn compile_and_publish_diagnostics(
.await;
for configuration_set in configuration_sets.iter_mut() {
// Trigger a compilation and get any diagnostics that were reported during it.
let diagnostics = configuration_set.trigger_compilation();
let diagnostics = configuration_set.trigger_compilation(&server_config);
// Publish those diagnostics.
publish_diagnostics_for_set(client, diagnostics, configuration_set).await;
}
Expand Down
35 changes: 20 additions & 15 deletions server/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright (c) ZeroC, Inc.

use diagnostic_ext::{clear_diagnostics, compile_and_publish_diagnostics, process_diagnostics};
use hover::try_into_hover_result;
use jump_definition::get_definition_span;
use notifications::{ShowNotification, ShowNotificationParams};
use crate::diagnostic_ext::{clear_diagnostics, compile_and_publish_diagnostics, process_diagnostics};
use crate::hover::try_into_hover_result;
use crate::jump_definition::get_definition_span;
use crate::notifications::{ShowNotification, ShowNotificationParams};
use crate::session::Session;
use crate::slice_config::compute_slice_options;
use std::{collections::HashMap, path::Path};
use tower_lsp::{jsonrpc::Error, lsp_types::*, Client, LanguageServer, LspService, Server};
use utils::{convert_slice_url_to_uri, url_to_sanitized_file_path, FindFile};

use crate::session::Session;

mod configuration_set;
mod diagnostic_ext;
mod hover;
Expand All @@ -35,7 +35,7 @@ struct Backend {

impl Backend {
pub fn new(client: tower_lsp::Client) -> Self {
let session = Session::new();
let session = Session::default();
Self { client, session }
}

Expand Down Expand Up @@ -77,19 +77,24 @@ impl Backend {
.await;

let mut configuration_sets = self.session.configuration_sets.lock().await;
let server_config = self.session.server_config.read().await;

let mut publish_map = HashMap::new();
let mut diagnostics = Vec::new();

// Process each configuration set that contains the changed file
for set in configuration_sets.iter_mut().filter(|set| {
set.slice_config.resolve_paths().into_iter().any(|f| {
let key_path = Path::new(&f);
let file_path = Path::new(file_name);
key_path == file_path || file_path.starts_with(key_path)
})
compute_slice_options(&server_config, &set.slice_config)
.references
.into_iter()
.any(|f| {
let key_path = Path::new(&f);
let file_path = Path::new(file_name);
key_path == file_path || file_path.starts_with(key_path)
})
}) {
// `trigger_compilation` compiles the configuration set's files and returns any diagnostics.
diagnostics.extend(set.trigger_compilation());
diagnostics.extend(set.trigger_compilation(&server_config));

// Update publish_map with files to be updated
publish_map.extend(
Expand Down Expand Up @@ -149,7 +154,7 @@ impl LanguageServer for Backend {

async fn initialized(&self, _: InitializedParams) {
// Now that the server and client are fully initialized, it's safe to compile and publish any diagnostics.
compile_and_publish_diagnostics(&self.client, &self.session.configuration_sets).await;
compile_and_publish_diagnostics(&self.client, &self.session).await;
}

async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
Expand All @@ -169,7 +174,7 @@ impl LanguageServer for Backend {
self.session.update_configurations_from_params(params).await;

// Trigger a compilation and publish the diagnostics for all files
compile_and_publish_diagnostics(&self.client, &self.session.configuration_sets).await;
compile_and_publish_diagnostics(&self.client, &self.session).await;
}

async fn goto_definition(
Expand Down
62 changes: 21 additions & 41 deletions server/src/session.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
// Copyright (c) ZeroC, Inc.

use crate::{
configuration_set::ConfigurationSet,
utils::{sanitize_path, url_to_sanitized_file_path},
};
use crate::configuration_set::ConfigurationSet;
use crate::slice_config::ServerConfig;
use crate::utils::{sanitize_path, url_to_sanitized_file_path};
use std::path::PathBuf;
use tokio::sync::{Mutex, RwLock};
use tower_lsp::lsp_types::DidChangeConfigurationParams;

#[derive(Debug, Default)]
pub struct Session {
/// This vector contains all of the configuration sets for the language server. Each element is a tuple containing
/// `SliceConfig` and `CompilationState`. The `SliceConfig` is used to determine which configuration set to use when
/// publishing diagnostics. The `CompilationState` is used to retrieve the diagnostics for a given file.
pub configuration_sets: Mutex<Vec<ConfigurationSet>>,
/// This is the root path of the workspace. It is used to resolve relative paths in the configuration.
pub root_path: RwLock<Option<PathBuf>>,
/// This is the path to the built-in Slice files that are included with the extension.
pub built_in_slice_path: RwLock<String>,
/// Configuration that affects the entire server.
pub server_config: RwLock<ServerConfig>,
}

impl Session {
pub fn new() -> Self {
Self {
configuration_sets: Mutex::new(Vec::new()),
root_path: RwLock::new(None),
built_in_slice_path: RwLock::new(String::new()),
}
}

// Update the properties of the session from `InitializeParams`
pub async fn update_from_initialize_params(
&self,
params: tower_lsp::lsp_types::InitializeParams,
) {
let initialization_options = params.initialization_options;

// Use the root_uri if it exists temporarily as we cannot access configuration until
// after initialization. Additionally, LSP may provide the windows path with escaping or a lowercase
// drive letter. To fix this, we convert the path to a URL and then back to a path.
let workspace_root_path = params
.root_uri
.and_then(|uri| url_to_sanitized_file_path(&uri))
.map(PathBuf::from)
.map(|path| path.display().to_string())
.expect("`root_uri` was not sent by the client, or was malformed");

// This is the path to the built-in Slice files that are included with the extension. It should always
// be present.
let built_in_slice_path = initialization_options
Expand All @@ -44,43 +44,28 @@ impl Session {
.map(sanitize_path)
.expect("builtInSlicePath not found in initialization options");

// Use the root_uri if it exists temporarily as we cannot access configuration until
// after initialization. Additionally, LSP may provide the windows path with escaping or a lowercase
// drive letter. To fix this, we convert the path to a URL and then back to a path.
let root_path = params
.root_uri
.and_then(|uri| url_to_sanitized_file_path(&uri))
.map(PathBuf::from)
.expect("`root_uri` was not sent by the client, or was malformed");
*self.server_config.write().await = ServerConfig { workspace_root_path, built_in_slice_path };

// Load any user configuration from the 'slice.configurations' option.
let configuration_sets = initialization_options
.as_ref()
.and_then(|opts| opts.get("configurations"))
.and_then(|v| v.as_array())
.map(|arr| {
ConfigurationSet::parse_configuration_sets(arr, &root_path, &built_in_slice_path)
})
.map(|arr| ConfigurationSet::parse_configuration_sets(arr))
.unwrap_or_default();

*self.built_in_slice_path.write().await = built_in_slice_path;
*self.root_path.write().await = Some(root_path);
self.update_configurations(configuration_sets).await;
}

// Update the configuration sets from the `DidChangeConfigurationParams` notification.
pub async fn update_configurations_from_params(&self, params: DidChangeConfigurationParams) {
let built_in_path = &self.built_in_slice_path.read().await;
let root_path_guard = self.root_path.read().await;
let root_path = (*root_path_guard).clone().expect("root_path not set");

// Parse the configurations from the notification
let configurations = params
.settings
.get("slice")
.and_then(|v| v.get("configurations"))
.and_then(|v| v.as_array())
.map(|arr| ConfigurationSet::parse_configuration_sets(arr, &root_path, built_in_path))
.map(|arr| ConfigurationSet::parse_configuration_sets(arr))
.unwrap_or_default();

// Update the configuration sets
Expand All @@ -92,14 +77,9 @@ impl Session {
async fn update_configurations(&self, mut configurations: Vec<ConfigurationSet>) {
// Insert the default configuration set if needed
if configurations.is_empty() {
let root_path = self.root_path.read().await;
let built_in_slice_path = self.built_in_slice_path.read().await;
let default =
ConfigurationSet::new(root_path.clone().unwrap(), built_in_slice_path.clone());
configurations.push(default);
configurations.push(ConfigurationSet::default());
}

let mut configuration_sets = self.configuration_sets.lock().await;
*configuration_sets = configurations;
*self.configuration_sets.lock().await = configurations;
}
}
Loading