Skip to content

Commit

Permalink
feat: support workspace folders configuration (#10488)
Browse files Browse the repository at this point in the history
Ref #8643
  • Loading branch information
kitsonk authored May 10, 2021
1 parent 33b1a6e commit 84733d9
Show file tree
Hide file tree
Showing 9 changed files with 456 additions and 194 deletions.
4 changes: 4 additions & 0 deletions cli/bench/fixtures/initialize_params.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"willSaveWaitUntil": true,
"didSave": true
}
},
"workspace": {
"configuration": true,
"workspaceFolders": true
}
}
}
48 changes: 48 additions & 0 deletions cli/bench/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@ impl LspClient {
}
}

fn read_request<R>(&mut self) -> Result<(u64, String, Option<R>), AnyError>
where
R: de::DeserializeOwned,
{
loop {
if let LspMessage::Request(id, method, maybe_params) = self.read()? {
if let Some(p) = maybe_params {
let params = serde_json::from_value(p)?;
return Ok((id, method, Some(params)));
} else {
return Ok((id, method, None));
}
}
}
}

fn write(&mut self, value: Value) -> Result<(), AnyError> {
let value_str = value.to_string();
let msg = format!(
Expand Down Expand Up @@ -222,6 +238,18 @@ impl LspClient {
}
}

fn write_response<V>(&mut self, id: u64, result: V) -> Result<(), AnyError>
where
V: Serialize,
{
let value = json!({
"jsonrpc": "2.0",
"id": id,
"result": result
});
self.write(value)
}

fn write_notification<S, V>(
&mut self,
method: S,
Expand Down Expand Up @@ -266,6 +294,16 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
}),
)?;

let (id, method, _): (u64, String, Option<Value>) = client.read_request()?;
assert_eq!(method, "workspace/configuration");

client.write_response(
id,
json!({
"enable": true
}),
)?;

let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?;
Expand Down Expand Up @@ -328,6 +366,16 @@ fn bench_startup_shutdown(deno_exe: &Path) -> Result<Duration, AnyError> {
}),
)?;

let (id, method, _): (u64, String, Option<Value>) = client.read_request()?;
assert_eq!(method, "workspace/configuration");

client.write_response(
id,
json!({
"enable": true
}),
)?;

let (method, _): (String, Option<Value>) = client.read_notification()?;
assert_eq!(method, "textDocument/publishDiagnostics");
let (method, _): (String, Option<Value>) = client.read_notification()?;
Expand Down
53 changes: 53 additions & 0 deletions cli/lsp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,56 @@ with Deno:
textDocument: TextDocumentIdentifier;
}
```

## Settings

There are several settings that the language server supports for a workspace:

- `deno.enable`
- `deno.config`
- `deno.import_map`
- `deno.code_lens.implementations`
- `deno.code_lens.references`
- `deno.code_lens.references_all_functions`
- `deno.suggest.complete_function_calls`
- `deno.suggest.names`
- `deno.suggest.paths`
- `deno.suggest.auto_imports`
- `deno.imports.hosts`
- `deno.lint`
- `deno.unstable`

There are settings that are support on a per resource basis by the language
server:

- `deno.enable`

There are several points in the process where Deno analyzes these settings.
First, when the `initialize` request from the client, the
`initializationOptions` will be assumed to be an object that represents the
`deno` namespace of options. For example, the following value:

```json
{
"enable": true,
"unstable": true
}
```

Would enable Deno with the unstable APIs for this instance of the language
server.

When the language server receives a `workspace/didChangeConfiguration`
notification, it will assess if the client has indicated if it has a
`workspaceConfiguration` capability. If it does, it will send a
`workspace/configuration` request which will include a request for the workspace
configuration as well as the configuration of all URIs that the language server
is currently tracking.

If the client has the `workspaceConfiguration` capability, the language server
will send a configuration request for the URI when it received the
`textDocument/didOpen` notification in order to get the resources specific
settings.

If the client does not have the `workspaceConfiguration` capability, the
language server will assume the workspace setting applies to all resources.
10 changes: 9 additions & 1 deletion cli/lsp/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use lspower::lsp::TextDocumentSyncCapability;
use lspower::lsp::TextDocumentSyncKind;
use lspower::lsp::TextDocumentSyncOptions;
use lspower::lsp::WorkDoneProgressOptions;
use lspower::lsp::WorkspaceFoldersServerCapabilities;
use lspower::lsp::WorkspaceServerCapabilities;

use super::semantic_tokens::get_legend;

Expand Down Expand Up @@ -132,7 +134,13 @@ pub fn server_capabilities(
},
),
),
workspace: None,
workspace: Some(WorkspaceServerCapabilities {
workspace_folders: Some(WorkspaceFoldersServerCapabilities {
supported: Some(true),
change_notifications: None,
}),
file_operations: None,
}),
experimental: None,
linked_editing_range_provider: None,
moniker_provider: None,
Expand Down
109 changes: 103 additions & 6 deletions cli/lsp/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ use deno_core::serde::Deserialize;
use deno_core::serde_json;
use deno_core::serde_json::Value;
use deno_core::url::Url;
use deno_core::ModuleSpecifier;
use lspower::jsonrpc::Error as LSPError;
use lspower::jsonrpc::Result as LSPResult;
use lspower::lsp;
use std::collections::HashMap;

pub const SETTINGS_SECTION: &str = "deno";

#[derive(Debug, Clone, Default)]
pub struct ClientCapabilities {
pub status_notification: bool,
Expand Down Expand Up @@ -84,19 +87,43 @@ impl Default for ImportCompletionSettings {
}
}

/// Deno language server specific settings that can be applied uniquely to a
/// specifier.
#[derive(Debug, Default, Clone, Deserialize)]
pub struct SpecifierSettings {
/// A flag that indicates if Deno is enabled for this specifier or not.
pub enable: bool,
}

/// Deno language server specific settings that are applied to a workspace.
#[derive(Debug, Default, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkspaceSettings {
/// A flag that indicates if Deno is enabled for the workspace.
pub enable: bool,

/// An option that points to a path string of the tsconfig file to apply to
/// code within the workspace.
pub config: Option<String>,

/// An option that points to a path string of the import map to apply to the
/// code within the workspace.
pub import_map: Option<String>,

/// Code lens specific settings for the workspace.
#[serde(default)]
pub code_lens: CodeLensSettings,
#[serde(default)]

/// Suggestion (auto-completion) settings for the workspace.
pub suggest: CompletionSettings,

/// A flag that indicates if linting is enabled for the workspace.
#[serde(default)]
pub lint: bool,

/// A flag that indicates if Dene should validate code against the unstable
/// APIs for the workspace.
#[serde(default)]
pub unstable: bool,
}
Expand All @@ -113,15 +140,21 @@ impl WorkspaceSettings {
pub struct Config {
pub client_capabilities: ClientCapabilities,
pub root_uri: Option<Url>,
pub settings: WorkspaceSettings,
pub specifier_settings: HashMap<ModuleSpecifier, SpecifierSettings>,
pub workspace_settings: WorkspaceSettings,
}

impl Config {
pub fn update(&mut self, value: Value) -> LSPResult<()> {
let settings: WorkspaceSettings = serde_json::from_value(value)
.map_err(|err| LSPError::invalid_params(err.to_string()))?;
self.settings = settings;
Ok(())
pub fn contains(&self, specifier: &ModuleSpecifier) -> bool {
self.specifier_settings.contains_key(specifier)
}

pub fn specifier_enabled(&self, specifier: &ModuleSpecifier) -> bool {
if let Some(settings) = self.specifier_settings.get(specifier) {
settings.enable
} else {
self.workspace_settings.enable
}
}

#[allow(clippy::redundant_closure_call)]
Expand Down Expand Up @@ -154,4 +187,68 @@ impl Config {
.unwrap_or(false);
}
}

pub fn update_specifier(
&mut self,
specifier: ModuleSpecifier,
value: Value,
) -> LSPResult<()> {
let settings: SpecifierSettings = serde_json::from_value(value)
.map_err(|err| LSPError::invalid_params(err.to_string()))?;
self.specifier_settings.insert(specifier, settings);
Ok(())
}

pub fn update_workspace(&mut self, value: Value) -> LSPResult<()> {
let settings: WorkspaceSettings = serde_json::from_value(value)
.map_err(|err| LSPError::invalid_params(err.to_string()))?;
self.workspace_settings = settings;
self.specifier_settings = HashMap::new();
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use deno_core::resolve_url;
use deno_core::serde_json::json;

#[test]
fn test_config_contains() {
let mut config = Config::default();
let specifier = resolve_url("https://deno.land/x/a.ts").unwrap();
assert!(!config.contains(&specifier));
config
.update_specifier(
specifier.clone(),
json!({
"enable": true
}),
)
.expect("could not update specifier");
assert!(config.contains(&specifier));
}

#[test]
fn test_config_specifier_enabled() {
let mut config = Config::default();
let specifier = resolve_url("file:///a.ts").unwrap();
assert!(!config.specifier_enabled(&specifier));
config
.update_workspace(json!({
"enable": true
}))
.expect("could not update");
assert!(config.specifier_enabled(&specifier));
config
.update_specifier(
specifier.clone(),
json!({
"enable": false
}),
)
.expect("could not update");
assert!(!config.specifier_enabled(&specifier));
}
}
Loading

0 comments on commit 84733d9

Please sign in to comment.