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

feat: support workspace folders configuration #10488

Merged
merged 7 commits into from
May 10, 2021
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
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