mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00:00
feat(lsp): support deno.enablePaths setting (#13978)
Ref: denoland/vscode_deno#633
This commit is contained in:
parent
daa7c6d32a
commit
1414dc503b
8 changed files with 534 additions and 72 deletions
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -330,7 +428,7 @@ impl Config {
|
|||
specifier: s.clone(),
|
||||
client_uri: u.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn set_specifier_settings(
|
||||
|
@ -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
|
||||
|
@ -389,9 +463,43 @@ 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");
|
||||
|
@ -399,6 +507,7 @@ mod tests {
|
|||
config.get_workspace_settings(),
|
||||
WorkspaceSettings {
|
||||
enable: false,
|
||||
enable_paths: Vec::new(),
|
||||
cache: None,
|
||||
certificate_stores: None,
|
||||
config: None,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue