Skip to content

Commit

Permalink
feat(lsp): support deno.enablePaths setting (denoland#13978)
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Mar 23, 2022
1 parent 44de402 commit 057748b
Show file tree
Hide file tree
Showing 8 changed files with 533 additions and 71 deletions.
2 changes: 1 addition & 1 deletion cli/lsp/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub fn server_capabilities(
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: None,
change_notifications: Some(OneOf::Left(true)),
}),
file_operations: None,
}),
Expand Down
175 changes: 142 additions & 33 deletions cli/lsp/config.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.

use super::client::Client;
use super::logging::lsp_log;
use crate::fs_util;
use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::serde_json;
use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use lsp::WorkspaceFolder;
use lspower::lsp;
use std::collections::BTreeMap;
use std::collections::HashMap;
Expand Down Expand Up @@ -128,6 +130,10 @@ impl Default for ImportCompletionSettings {
pub struct SpecifierSettings {
/// A flag that indicates if Deno is enabled for this specifier or not.
pub enable: bool,
/// A list of paths, using the workspace folder as a base that should be Deno
/// enabled.
#[serde(default)]
pub enable_paths: Vec<String>,
/// Code lens specific settings for the resource.
#[serde(default)]
pub code_lens: CodeLensSpecifierSettings,
Expand All @@ -141,6 +147,10 @@ pub struct WorkspaceSettings {
#[serde(default)]
pub enable: bool,

/// A list of paths, using the root_uri as a base that should be Deno enabled.
#[serde(default)]
pub enable_paths: Vec<String>,

/// An option that points to a path string of the path to utilise as the
/// cache/DENO_DIR for the language server.
pub cache: Option<String>,
Expand Down Expand Up @@ -198,14 +208,27 @@ impl WorkspaceSettings {
#[derive(Debug, Clone, Default)]
pub struct ConfigSnapshot {
pub client_capabilities: ClientCapabilities,
pub enabled_paths: HashMap<String, Vec<String>>,
pub settings: Settings,
pub workspace_folders: Option<Vec<lsp::WorkspaceFolder>>,
}

impl ConfigSnapshot {
/// Determine if the provided specifier is enabled or not.
pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if let Some(settings) = self.settings.specifiers.get(specifier) {
settings.1.enable
if !self.enabled_paths.is_empty() {
let specifier_str = specifier.to_string();
for (workspace, enabled_paths) in self.enabled_paths.iter() {
if specifier_str.starts_with(workspace) {
return enabled_paths
.iter()
.any(|path| specifier_str.starts_with(path));
}
}
}
if let Some((_, SpecifierSettings { enable, .. })) =
self.settings.specifiers.get(specifier)
{
*enable
} else {
self.settings.workspace.enable
}
Expand All @@ -228,14 +251,19 @@ pub struct Settings {
#[derive(Debug)]
pub struct Config {
pub client_capabilities: ClientCapabilities,
enabled_paths: HashMap<String, Vec<String>>,
pub root_uri: Option<ModuleSpecifier>,
settings: Settings,
pub workspace_folders: Option<Vec<WorkspaceFolder>>,
pub workspace_folders: Option<Vec<(ModuleSpecifier, lsp::WorkspaceFolder)>>,
}

impl Config {
pub fn new() -> Self {
Self {
client_capabilities: ClientCapabilities::default(),
enabled_paths: Default::default(),
/// Root provided by the initialization parameters.
root_uri: None,
settings: Default::default(),
workspace_folders: None,
}
Expand All @@ -259,8 +287,8 @@ impl Config {
pub fn snapshot(&self) -> Arc<ConfigSnapshot> {
Arc::new(ConfigSnapshot {
client_capabilities: self.client_capabilities.clone(),
enabled_paths: self.enabled_paths.clone(),
settings: self.settings.clone(),
workspace_folders: self.workspace_folders.clone(),
})
}

Expand All @@ -269,6 +297,16 @@ impl Config {
}

pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if !self.enabled_paths.is_empty() {
let specifier_str = specifier.to_string();
for (workspace, enabled_paths) in self.enabled_paths.iter() {
if specifier_str.starts_with(workspace) {
return enabled_paths
.iter()
.any(|path| specifier_str.starts_with(path));
}
}
}
self
.settings
.specifiers
Expand Down Expand Up @@ -321,6 +359,66 @@ impl Config {
}
}

/// Given the configured workspaces or root URI and the their settings,
/// update and resolve any paths that should be enabled
pub async fn update_enabled_paths(&mut self, client: Client) -> bool {
if let Some(workspace_folders) = self.workspace_folders.clone() {
let mut touched = false;
for (workspace, folder) in workspace_folders {
if let Ok(settings) = client.specifier_configuration(&folder.uri).await
{
if self.update_enabled_paths_entry(&workspace, settings.enable_paths)
{
touched = true;
}
}
}
touched
} else if let Some(root_uri) = self.root_uri.clone() {
self.update_enabled_paths_entry(
&root_uri,
self.settings.workspace.enable_paths.clone(),
)
} else {
false
}
}

/// Update a specific entry in the enabled paths for a given workspace.
fn update_enabled_paths_entry(
&mut self,
workspace: &ModuleSpecifier,
enabled_paths: Vec<String>,
) -> bool {
let workspace = fs_util::ensure_directory_specifier(workspace.clone());
let key = workspace.to_string();
let mut touched = false;
if !enabled_paths.is_empty() {
if let Ok(workspace_path) = fs_util::specifier_to_file_path(&workspace) {
let mut paths = Vec::new();
for path in &enabled_paths {
let fs_path = workspace_path.join(path);
match ModuleSpecifier::from_file_path(fs_path) {
Ok(path_uri) => {
paths.push(path_uri.to_string());
}
Err(_) => {
lsp_log!("Unable to resolve a file path for `deno.enablePath` from \"{}\" for workspace \"{}\".", path, workspace);
}
}
}
if !paths.is_empty() {
touched = true;
self.enabled_paths.insert(key, paths);
}
}
} else {
touched = true;
self.enabled_paths.remove(&key);
}
touched
}

pub fn get_specifiers_with_client_uris(&self) -> Vec<SpecifierWithClientUri> {
self
.settings
Expand All @@ -330,7 +428,7 @@ impl Config {
specifier: s.clone(),
client_uri: u.clone(),
})
.collect::<Vec<_>>()
.collect()
}

pub fn set_specifier_settings(
Expand All @@ -352,33 +450,9 @@ mod tests {
use deno_core::resolve_url;
use deno_core::serde_json::json;

#[derive(Debug, Default)]
struct MockLanguageServer;

#[lspower::async_trait]
impl lspower::LanguageServer for MockLanguageServer {
async fn initialize(
&self,
_params: lspower::lsp::InitializeParams,
) -> lspower::jsonrpc::Result<lsp::InitializeResult> {
Ok(lspower::lsp::InitializeResult {
capabilities: lspower::lsp::ServerCapabilities::default(),
server_info: None,
})
}

async fn shutdown(&self) -> lspower::jsonrpc::Result<()> {
Ok(())
}
}

fn setup() -> Config {
Config::new()
}

#[test]
fn test_config_specifier_enabled() {
let mut config = setup();
let mut config = Config::new();
let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier));
config
Expand All @@ -389,16 +463,51 @@ mod tests {
assert!(config.specifier_enabled(&specifier));
}

#[test]
fn test_config_snapshot_specifier_enabled() {
let mut config = Config::new();
let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier));
config
.set_workspace_settings(json!({
"enable": true
}))
.expect("could not update");
let config_snapshot = config.snapshot();
assert!(config_snapshot.specifier_enabled(&specifier));
}

#[test]
fn test_config_specifier_enabled_path() {
let mut config = Config::new();
let specifier_a = resolve_url("file:///project/worker/a.ts").unwrap();
let specifier_b = resolve_url("file:///project/other/b.ts").unwrap();
assert!(!config.specifier_enabled(&specifier_a));
assert!(!config.specifier_enabled(&specifier_b));
let mut enabled_paths = HashMap::new();
enabled_paths.insert(
"file:///project/".to_string(),
vec!["file:///project/worker/".to_string()],
);
config.enabled_paths = enabled_paths;
assert!(config.specifier_enabled(&specifier_a));
assert!(!config.specifier_enabled(&specifier_b));
let config_snapshot = config.snapshot();
assert!(config_snapshot.specifier_enabled(&specifier_a));
assert!(!config_snapshot.specifier_enabled(&specifier_b));
}

#[test]
fn test_set_workspace_settings_defaults() {
let mut config = setup();
let mut config = Config::new();
config
.set_workspace_settings(json!({}))
.expect("could not update");
assert_eq!(
config.get_workspace_settings(),
WorkspaceSettings {
enable: false,
enable_paths: Vec::new(),
cache: None,
certificate_stores: None,
config: None,
Expand Down
6 changes: 4 additions & 2 deletions cli/lsp/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,8 @@ async fn generate_deps_diagnostics(
break;
}
let mut diagnostics = Vec::new();
if config.specifier_enabled(document.specifier()) {
let specifier = document.specifier();
if config.specifier_enabled(specifier) {
for (_, dependency) in document.dependencies() {
diagnose_dependency(
&mut diagnostics,
Expand All @@ -866,7 +867,7 @@ async fn generate_deps_diagnostics(
}
}
diagnostics_vec.push((
document.specifier().clone(),
specifier.clone(),
document.maybe_lsp_version(),
diagnostics,
));
Expand Down Expand Up @@ -985,6 +986,7 @@ let c: number = "a";
specifier.clone(),
SpecifierSettings {
enable: false,
enable_paths: Vec::new(),
code_lens: Default::default(),
},
),
Expand Down
Loading

0 comments on commit 057748b

Please sign in to comment.