mirror of
https://github.com/denoland/deno.git
synced 2025-01-08 15:19:40 -05:00
feat(lsp): registry auto discovery (#10813)
Closes: #10194 Fixes: #10468
This commit is contained in:
parent
9abb899f5f
commit
bb5bf91067
8 changed files with 355 additions and 155 deletions
|
@ -5,9 +5,8 @@ The Deno Language Server provides a server implementation of the
|
||||||
which is specifically tailored to provide a _Deno_ view of code. It is
|
which is specifically tailored to provide a _Deno_ view of code. It is
|
||||||
integrated into the command line and can be started via the `lsp` sub-command.
|
integrated into the command line and can be started via the `lsp` sub-command.
|
||||||
|
|
||||||
> :warning: The Language Server is highly experimental and far from feature
|
> :warning: The Language Server is experimental and not feature complete. This
|
||||||
> complete. This document gives an overview of the structure of the language
|
> document gives an overview of the structure of the language server.
|
||||||
> server.
|
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
|
@ -15,6 +14,60 @@ When the language server is started, a `LanguageServer` instance is created
|
||||||
which holds all of the state of the language server. It also defines all of the
|
which holds all of the state of the language server. It also defines all of the
|
||||||
methods that the client calls via the Language Server RPC protocol.
|
methods that the client calls via the Language Server RPC protocol.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
There are several settings that the language server supports for a workspace:
|
||||||
|
|
||||||
|
- `deno.enable`
|
||||||
|
- `deno.config`
|
||||||
|
- `deno.importMap`
|
||||||
|
- `deno.codeLens.implementations`
|
||||||
|
- `deno.codeLens.references`
|
||||||
|
- `deno.codeLens.referencesAllFunctions`
|
||||||
|
- `deno.suggest.completeFunctionCalls`
|
||||||
|
- `deno.suggest.names`
|
||||||
|
- `deno.suggest.paths`
|
||||||
|
- `deno.suggest.autoImports`
|
||||||
|
- `deno.suggest.imports.autoDiscover`
|
||||||
|
- `deno.suggest.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.
|
||||||
|
|
||||||
## Custom requests
|
## Custom requests
|
||||||
|
|
||||||
The LSP currently supports the following custom requests. A client should
|
The LSP currently supports the following custom requests. A client should
|
||||||
|
@ -62,55 +115,27 @@ with Deno:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Settings
|
## Custom notifications
|
||||||
|
|
||||||
There are several settings that the language server supports for a workspace:
|
There is currently one custom notification that is send from the server to the
|
||||||
|
client:
|
||||||
|
|
||||||
- `deno.enable`
|
- `deno/registryStatus` - when `deno.suggest.imports.autoDiscover` is `true` and
|
||||||
- `deno.config`
|
an origin for an import being added to a document is not explicitly set in
|
||||||
- `deno.import_map`
|
`deno.suggest.imports.hosts`, the origin will be checked and the notification
|
||||||
- `deno.code_lens.implementations`
|
will be sent to the client of the status.
|
||||||
- `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
|
When receiving the notification, if the param `suggestion` is `true`, the
|
||||||
server:
|
client should offer the user the choice to enable the origin and add it to the
|
||||||
|
configuration for `deno.suggest.imports.hosts`. If `suggestion` is `false` the
|
||||||
|
client should add it to the configuration of as `false` to stop the language
|
||||||
|
server from attempting to detect if suggestions are supported.
|
||||||
|
|
||||||
- `deno.enable`
|
The params for the notification are:
|
||||||
|
|
||||||
There are several points in the process where Deno analyzes these settings.
|
```ts
|
||||||
First, when the `initialize` request from the client, the
|
interface RegistryStatusNotificationParams {
|
||||||
`initializationOptions` will be assumed to be an object that represents the
|
origin: string;
|
||||||
`deno` namespace of options. For example, the following value:
|
suggestions: boolean;
|
||||||
|
}
|
||||||
```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.
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use super::analysis;
|
use super::analysis;
|
||||||
use super::language_server;
|
use super::language_server;
|
||||||
|
use super::lsp_custom;
|
||||||
use super::tsc;
|
use super::tsc;
|
||||||
|
|
||||||
use crate::fs_util::is_supported_ext;
|
use crate::fs_util::is_supported_ext;
|
||||||
|
@ -9,6 +10,7 @@ use crate::media_type::MediaType;
|
||||||
|
|
||||||
use deno_core::normalize_path;
|
use deno_core::normalize_path;
|
||||||
use deno_core::resolve_path;
|
use deno_core::resolve_path;
|
||||||
|
use deno_core::resolve_url;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
use deno_core::serde::Serialize;
|
use deno_core::serde::Serialize;
|
||||||
use deno_core::url::Position;
|
use deno_core::url::Position;
|
||||||
|
@ -34,6 +36,64 @@ pub struct CompletionItemData {
|
||||||
pub tsc: Option<tsc::CompletionItemData>,
|
pub tsc: Option<tsc::CompletionItemData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the origin can be auto-configured for completions, and if so, send
|
||||||
|
/// a notification to the client.
|
||||||
|
async fn check_auto_config_registry(
|
||||||
|
url_str: &str,
|
||||||
|
snapshot: &language_server::StateSnapshot,
|
||||||
|
client: lspower::Client,
|
||||||
|
) {
|
||||||
|
// check to see if auto discovery is enabled
|
||||||
|
if snapshot
|
||||||
|
.config
|
||||||
|
.settings
|
||||||
|
.workspace
|
||||||
|
.suggest
|
||||||
|
.imports
|
||||||
|
.auto_discover
|
||||||
|
{
|
||||||
|
if let Ok(specifier) = resolve_url(url_str) {
|
||||||
|
let scheme = specifier.scheme();
|
||||||
|
let path = &specifier[Position::BeforePath..];
|
||||||
|
if scheme.starts_with("http")
|
||||||
|
&& !path.is_empty()
|
||||||
|
&& url_str.ends_with(path)
|
||||||
|
{
|
||||||
|
// check to see if this origin is already explicitly set
|
||||||
|
let in_config = snapshot
|
||||||
|
.config
|
||||||
|
.settings
|
||||||
|
.workspace
|
||||||
|
.suggest
|
||||||
|
.imports
|
||||||
|
.hosts
|
||||||
|
.iter()
|
||||||
|
.any(|(h, _)| {
|
||||||
|
resolve_url(h).map(|u| u.origin()) == Ok(specifier.origin())
|
||||||
|
});
|
||||||
|
// if it isn't in the configuration, we will check to see if it supports
|
||||||
|
// suggestions and send a notification to the client.
|
||||||
|
if !in_config {
|
||||||
|
let origin = specifier.origin().ascii_serialization();
|
||||||
|
let suggestions = snapshot
|
||||||
|
.module_registries
|
||||||
|
.fetch_config(&origin)
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
client
|
||||||
|
.send_custom_notification::<lsp_custom::RegistryStateNotification>(
|
||||||
|
lsp_custom::RegistryStateNotificationParams {
|
||||||
|
origin,
|
||||||
|
suggestions,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a specifier, a position, and a snapshot, optionally return a
|
/// Given a specifier, a position, and a snapshot, optionally return a
|
||||||
/// completion response, which will be valid import completions for the specific
|
/// completion response, which will be valid import completions for the specific
|
||||||
/// context.
|
/// context.
|
||||||
|
@ -41,6 +101,7 @@ pub async fn get_import_completions(
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
position: &lsp::Position,
|
position: &lsp::Position,
|
||||||
state_snapshot: &language_server::StateSnapshot,
|
state_snapshot: &language_server::StateSnapshot,
|
||||||
|
client: lspower::Client,
|
||||||
) -> Option<lsp::CompletionResponse> {
|
) -> Option<lsp::CompletionResponse> {
|
||||||
if let Ok(Some(source)) = state_snapshot.documents.content(specifier) {
|
if let Ok(Some(source)) = state_snapshot.documents.content(specifier) {
|
||||||
let media_type = MediaType::from(specifier);
|
let media_type = MediaType::from(specifier);
|
||||||
|
@ -58,6 +119,8 @@ pub async fn get_import_completions(
|
||||||
}
|
}
|
||||||
// completion of modules from a module registry or cache
|
// completion of modules from a module registry or cache
|
||||||
if !current_specifier.is_empty() {
|
if !current_specifier.is_empty() {
|
||||||
|
check_auto_config_registry(¤t_specifier, state_snapshot, client)
|
||||||
|
.await;
|
||||||
let offset = if position.character > range.start.character {
|
let offset = if position.character > range.start.character {
|
||||||
(position.character - range.start.character) as usize
|
(position.character - range.start.character) as usize
|
||||||
} else {
|
} else {
|
||||||
|
@ -808,11 +871,17 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_import_completions() {
|
async fn test_get_workspace_completions() {
|
||||||
let specifier = resolve_url("file:///a/b/c.ts").unwrap();
|
let specifier = resolve_url("file:///a/b/c.ts").unwrap();
|
||||||
let position = lsp::Position {
|
let range = lsp::Range {
|
||||||
line: 0,
|
start: lsp::Position {
|
||||||
character: 21,
|
line: 0,
|
||||||
|
character: 20,
|
||||||
|
},
|
||||||
|
end: lsp::Position {
|
||||||
|
line: 0,
|
||||||
|
character: 21,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
let state_snapshot = setup(
|
let state_snapshot = setup(
|
||||||
&[
|
&[
|
||||||
|
@ -822,32 +891,29 @@ mod tests {
|
||||||
&[("https://deno.land/x/a/b/c.ts", "console.log(1);\n")],
|
&[("https://deno.land/x/a/b/c.ts", "console.log(1);\n")],
|
||||||
);
|
);
|
||||||
let actual =
|
let actual =
|
||||||
get_import_completions(&specifier, &position, &state_snapshot).await;
|
get_workspace_completions(&specifier, "h", &range, &state_snapshot);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual,
|
actual,
|
||||||
Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
vec![lsp::CompletionItem {
|
||||||
is_incomplete: false,
|
label: "https://deno.land/x/a/b/c.ts".to_string(),
|
||||||
items: vec![lsp::CompletionItem {
|
kind: Some(lsp::CompletionItemKind::File),
|
||||||
label: "https://deno.land/x/a/b/c.ts".to_string(),
|
detail: Some("(remote)".to_string()),
|
||||||
kind: Some(lsp::CompletionItemKind::File),
|
sort_text: Some("1".to_string()),
|
||||||
detail: Some("(remote)".to_string()),
|
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||||
sort_text: Some("1".to_string()),
|
range: lsp::Range {
|
||||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
start: lsp::Position {
|
||||||
range: lsp::Range {
|
line: 0,
|
||||||
start: lsp::Position {
|
character: 20
|
||||||
line: 0,
|
|
||||||
character: 20
|
|
||||||
},
|
|
||||||
end: lsp::Position {
|
|
||||||
line: 0,
|
|
||||||
character: 21,
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new_text: "https://deno.land/x/a/b/c.ts".to_string(),
|
end: lsp::Position {
|
||||||
})),
|
line: 0,
|
||||||
..Default::default()
|
character: 21,
|
||||||
}]
|
}
|
||||||
}))
|
},
|
||||||
|
new_text: "https://deno.land/x/a/b/c.ts".to_string(),
|
||||||
|
})),
|
||||||
|
..Default::default()
|
||||||
|
}]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub struct ClientCapabilities {
|
||||||
pub line_folding_only: bool,
|
pub line_folding_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CodeLensSettings {
|
pub struct CodeLensSettings {
|
||||||
/// Flag for providing implementation code lenses.
|
/// Flag for providing implementation code lenses.
|
||||||
|
@ -53,16 +53,20 @@ impl Default for CodeLensSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
fn is_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompletionSettings {
|
pub struct CompletionSettings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub complete_function_calls: bool,
|
pub complete_function_calls: bool,
|
||||||
#[serde(default)]
|
#[serde(default = "is_true")]
|
||||||
pub names: bool,
|
pub names: bool,
|
||||||
#[serde(default)]
|
#[serde(default = "is_true")]
|
||||||
pub paths: bool,
|
pub paths: bool,
|
||||||
#[serde(default)]
|
#[serde(default = "is_true")]
|
||||||
pub auto_imports: bool,
|
pub auto_imports: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub imports: ImportCompletionSettings,
|
pub imports: ImportCompletionSettings,
|
||||||
|
@ -80,9 +84,15 @@ impl Default for CompletionSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ImportCompletionSettings {
|
pub struct ImportCompletionSettings {
|
||||||
|
/// A flag that indicates if non-explicitly set origins should be checked for
|
||||||
|
/// supporting import suggestions.
|
||||||
|
#[serde(default = "is_true")]
|
||||||
|
pub auto_discover: bool,
|
||||||
|
/// A map of origins which have had explicitly set if import suggestions are
|
||||||
|
/// enabled.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub hosts: HashMap<String, bool>,
|
pub hosts: HashMap<String, bool>,
|
||||||
}
|
}
|
||||||
|
@ -90,6 +100,7 @@ pub struct ImportCompletionSettings {
|
||||||
impl Default for ImportCompletionSettings {
|
impl Default for ImportCompletionSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
auto_discover: true,
|
||||||
hosts: HashMap::default(),
|
hosts: HashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,10 +115,11 @@ pub struct SpecifierSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deno language server specific settings that are applied to a workspace.
|
/// Deno language server specific settings that are applied to a workspace.
|
||||||
#[derive(Debug, Default, Clone, Deserialize)]
|
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct WorkspaceSettings {
|
pub struct WorkspaceSettings {
|
||||||
/// A flag that indicates if Deno is enabled for the workspace.
|
/// A flag that indicates if Deno is enabled for the workspace.
|
||||||
|
#[serde(default)]
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
|
|
||||||
/// An option that points to a path string of the config file to apply to
|
/// An option that points to a path string of the config file to apply to
|
||||||
|
@ -420,4 +432,38 @@ mod tests {
|
||||||
.expect("could not update");
|
.expect("could not update");
|
||||||
assert!(config.specifier_enabled(&specifier));
|
assert!(config.specifier_enabled(&specifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_set_workspace_settings_defaults() {
|
||||||
|
let config = setup();
|
||||||
|
config
|
||||||
|
.set_workspace_settings(json!({}))
|
||||||
|
.expect("could not update");
|
||||||
|
assert_eq!(
|
||||||
|
config.get_workspace_settings(),
|
||||||
|
WorkspaceSettings {
|
||||||
|
enable: false,
|
||||||
|
config: None,
|
||||||
|
import_map: None,
|
||||||
|
code_lens: CodeLensSettings {
|
||||||
|
implementations: false,
|
||||||
|
references: false,
|
||||||
|
references_all_functions: false,
|
||||||
|
},
|
||||||
|
internal_debug: false,
|
||||||
|
lint: false,
|
||||||
|
suggest: CompletionSettings {
|
||||||
|
complete_function_calls: false,
|
||||||
|
names: true,
|
||||||
|
paths: true,
|
||||||
|
auto_imports: true,
|
||||||
|
imports: ImportCompletionSettings {
|
||||||
|
auto_discover: true,
|
||||||
|
hosts: HashMap::new(),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unstable: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
use deno_core::error::anyhow;
|
use deno_core::error::anyhow;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::resolve_url;
|
use deno_core::resolve_url;
|
||||||
use deno_core::serde::Deserialize;
|
|
||||||
use deno_core::serde::Serialize;
|
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::serde_json::Value;
|
use deno_core::serde_json::Value;
|
||||||
|
@ -44,6 +42,7 @@ use super::config::SETTINGS_SECTION;
|
||||||
use super::diagnostics;
|
use super::diagnostics;
|
||||||
use super::diagnostics::DiagnosticSource;
|
use super::diagnostics::DiagnosticSource;
|
||||||
use super::documents::DocumentCache;
|
use super::documents::DocumentCache;
|
||||||
|
use super::lsp_custom;
|
||||||
use super::performance::Performance;
|
use super::performance::Performance;
|
||||||
use super::registries;
|
use super::registries;
|
||||||
use super::sources;
|
use super::sources;
|
||||||
|
@ -385,10 +384,9 @@ impl Inner {
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
if *enabled {
|
if *enabled {
|
||||||
info!("Enabling auto complete registry for: {}", registry);
|
info!("Enabling import suggestions for: {}", registry);
|
||||||
self.module_registries.enable(registry).await?;
|
self.module_registries.enable(registry).await?;
|
||||||
} else {
|
} else {
|
||||||
info!("Disabling auto complete registry for: {}", registry);
|
|
||||||
self.module_registries.disable(registry).await?;
|
self.module_registries.disable(registry).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -552,17 +550,11 @@ impl Inner {
|
||||||
async fn initialized(&mut self, _: InitializedParams) {
|
async fn initialized(&mut self, _: InitializedParams) {
|
||||||
// Check to see if we need to setup the import map
|
// Check to see if we need to setup the import map
|
||||||
if let Err(err) = self.update_import_map().await {
|
if let Err(err) = self.update_import_map().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
// Check to see if we need to setup any module registries
|
// Check to see if we need to setup any module registries
|
||||||
if let Err(err) = self.update_registries().await {
|
if let Err(err) = self.update_registries().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self
|
if self
|
||||||
|
@ -713,22 +705,13 @@ impl Inner {
|
||||||
|
|
||||||
self.update_debug_flag();
|
self.update_debug_flag();
|
||||||
if let Err(err) = self.update_import_map().await {
|
if let Err(err) = self.update_import_map().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if let Err(err) = self.update_registries().await {
|
if let Err(err) = self.update_registries().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if let Err(err) = self.update_tsconfig().await {
|
if let Err(err) = self.update_tsconfig().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
if let Err(err) = self.diagnostics_server.update() {
|
if let Err(err) = self.diagnostics_server.update() {
|
||||||
error!("{}", err);
|
error!("{}", err);
|
||||||
|
@ -748,10 +731,7 @@ impl Inner {
|
||||||
if let Some(import_map_uri) = &self.maybe_import_map_uri {
|
if let Some(import_map_uri) = &self.maybe_import_map_uri {
|
||||||
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
|
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
|
||||||
if let Err(err) = self.update_import_map().await {
|
if let Err(err) = self.update_import_map().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -759,10 +739,7 @@ impl Inner {
|
||||||
if let Some(config_uri) = &self.maybe_config_uri {
|
if let Some(config_uri) = &self.maybe_config_uri {
|
||||||
if params.changes.iter().any(|fe| *config_uri == fe.uri) {
|
if params.changes.iter().any(|fe| *config_uri == fe.uri) {
|
||||||
if let Err(err) = self.update_tsconfig().await {
|
if let Err(err) = self.update_tsconfig().await {
|
||||||
self
|
self.client.show_message(MessageType::Warning, err).await;
|
||||||
.client
|
|
||||||
.show_message(MessageType::Warning, err.to_string())
|
|
||||||
.await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1549,6 +1526,7 @@ impl Inner {
|
||||||
&specifier,
|
&specifier,
|
||||||
¶ms.text_document_position.position,
|
¶ms.text_document_position.position,
|
||||||
&self.snapshot()?,
|
&self.snapshot()?,
|
||||||
|
self.client.clone(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -2004,27 +1982,31 @@ impl Inner {
|
||||||
params: Option<Value>,
|
params: Option<Value>,
|
||||||
) -> LspResult<Option<Value>> {
|
) -> LspResult<Option<Value>> {
|
||||||
match method {
|
match method {
|
||||||
"deno/cache" => match params.map(serde_json::from_value) {
|
lsp_custom::CACHE_REQUEST => match params.map(serde_json::from_value) {
|
||||||
Some(Ok(params)) => self.cache(params).await,
|
Some(Ok(params)) => self.cache(params).await,
|
||||||
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
||||||
None => Err(LspError::invalid_params("Missing parameters")),
|
None => Err(LspError::invalid_params("Missing parameters")),
|
||||||
},
|
},
|
||||||
"deno/performance" => Ok(Some(self.get_performance())),
|
lsp_custom::PERFORMANCE_REQUEST => Ok(Some(self.get_performance())),
|
||||||
"deno/reloadImportRegistries" => self.reload_import_registries().await,
|
lsp_custom::RELOAD_IMPORT_REGISTRIES_REQUEST => {
|
||||||
"deno/virtualTextDocument" => match params.map(serde_json::from_value) {
|
self.reload_import_registries().await
|
||||||
Some(Ok(params)) => Ok(Some(
|
}
|
||||||
serde_json::to_value(self.virtual_text_document(params).await?)
|
lsp_custom::VIRTUAL_TEXT_DOCUMENT => {
|
||||||
.map_err(|err| {
|
match params.map(serde_json::from_value) {
|
||||||
error!(
|
Some(Ok(params)) => Ok(Some(
|
||||||
"Failed to serialize virtual_text_document response: {}",
|
serde_json::to_value(self.virtual_text_document(params).await?)
|
||||||
err
|
.map_err(|err| {
|
||||||
);
|
error!(
|
||||||
LspError::internal_error()
|
"Failed to serialize virtual_text_document response: {}",
|
||||||
})?,
|
err
|
||||||
)),
|
);
|
||||||
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
LspError::internal_error()
|
||||||
None => Err(LspError::invalid_params("Missing parameters")),
|
})?,
|
||||||
},
|
)),
|
||||||
|
Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
|
||||||
|
None => Err(LspError::invalid_params("Missing parameters")),
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Got a {} request, but no handler is defined", method);
|
error!("Got a {} request, but no handler is defined", method);
|
||||||
Err(LspError::method_not_found())
|
Err(LspError::method_not_found())
|
||||||
|
@ -2437,28 +2419,14 @@ impl lspower::LanguageServer for LanguageServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct CacheParams {
|
|
||||||
/// The document currently open in the editor. If there are no `uris`
|
|
||||||
/// supplied, the referrer will be cached.
|
|
||||||
referrer: TextDocumentIdentifier,
|
|
||||||
/// Any documents that have been specifically asked to be cached via the
|
|
||||||
/// command.
|
|
||||||
uris: Vec<TextDocumentIdentifier>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct VirtualTextDocumentParams {
|
|
||||||
text_document: TextDocumentIdentifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
// These are implementations of custom commands supported by the LSP
|
// These are implementations of custom commands supported by the LSP
|
||||||
impl Inner {
|
impl Inner {
|
||||||
/// Similar to `deno cache` on the command line, where modules will be cached
|
/// Similar to `deno cache` on the command line, where modules will be cached
|
||||||
/// in the Deno cache, including any of their dependencies.
|
/// in the Deno cache, including any of their dependencies.
|
||||||
async fn cache(&mut self, params: CacheParams) -> LspResult<Option<Value>> {
|
async fn cache(
|
||||||
|
&mut self,
|
||||||
|
params: lsp_custom::CacheParams,
|
||||||
|
) -> LspResult<Option<Value>> {
|
||||||
let mark = self.performance.mark("cache", Some(¶ms));
|
let mark = self.performance.mark("cache", Some(¶ms));
|
||||||
let referrer = self.url_map.normalize_url(¶ms.referrer.uri);
|
let referrer = self.url_map.normalize_url(¶ms.referrer.uri);
|
||||||
if !params.uris.is_empty() {
|
if !params.uris.is_empty() {
|
||||||
|
@ -2519,7 +2487,7 @@ impl Inner {
|
||||||
|
|
||||||
async fn virtual_text_document(
|
async fn virtual_text_document(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: VirtualTextDocumentParams,
|
params: lsp_custom::VirtualTextDocumentParams,
|
||||||
) -> LspResult<Option<String>> {
|
) -> LspResult<Option<String>> {
|
||||||
let mark = self
|
let mark = self
|
||||||
.performance
|
.performance
|
||||||
|
|
42
cli/lsp/lsp_custom.rs
Normal file
42
cli/lsp/lsp_custom.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::serde::Deserialize;
|
||||||
|
use deno_core::serde::Serialize;
|
||||||
|
use lspower::lsp;
|
||||||
|
|
||||||
|
pub const CACHE_REQUEST: &str = "deno/cache";
|
||||||
|
pub const PERFORMANCE_REQUEST: &str = "deno/performance";
|
||||||
|
pub const RELOAD_IMPORT_REGISTRIES_REQUEST: &str =
|
||||||
|
"deno/reloadImportRegistries";
|
||||||
|
pub const VIRTUAL_TEXT_DOCUMENT: &str = "deno/virtualTextDocument";
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CacheParams {
|
||||||
|
/// The document currently open in the editor. If there are no `uris`
|
||||||
|
/// supplied, the referrer will be cached.
|
||||||
|
pub referrer: lsp::TextDocumentIdentifier,
|
||||||
|
/// Any documents that have been specifically asked to be cached via the
|
||||||
|
/// command.
|
||||||
|
pub uris: Vec<lsp::TextDocumentIdentifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct RegistryStateNotificationParams {
|
||||||
|
pub origin: String,
|
||||||
|
pub suggestions: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RegistryStateNotification {}
|
||||||
|
|
||||||
|
impl lsp::notification::Notification for RegistryStateNotification {
|
||||||
|
type Params = RegistryStateNotificationParams;
|
||||||
|
|
||||||
|
const METHOD: &'static str = "deno/registryState";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct VirtualTextDocumentParams {
|
||||||
|
pub text_document: lsp::TextDocumentIdentifier,
|
||||||
|
}
|
|
@ -11,6 +11,7 @@ mod config;
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
mod documents;
|
mod documents;
|
||||||
pub(crate) mod language_server;
|
pub(crate) mod language_server;
|
||||||
|
mod lsp_custom;
|
||||||
mod path_to_regex;
|
mod path_to_regex;
|
||||||
mod performance;
|
mod performance;
|
||||||
mod registries;
|
mod registries;
|
||||||
|
|
|
@ -219,7 +219,7 @@ fn validate_config(config: &RegistryConfigurationJson) -> Result<(), AnyError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct RegistryConfigurationVariable {
|
pub(crate) struct RegistryConfigurationVariable {
|
||||||
/// The name of the variable.
|
/// The name of the variable.
|
||||||
key: String,
|
key: String,
|
||||||
/// The URL with variable substitutions of the endpoint that will provide
|
/// The URL with variable substitutions of the endpoint that will provide
|
||||||
|
@ -228,7 +228,7 @@ struct RegistryConfigurationVariable {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
struct RegistryConfiguration {
|
pub(crate) struct RegistryConfiguration {
|
||||||
/// A Express-like path which describes how URLs are composed for a registry.
|
/// A Express-like path which describes how URLs are composed for a registry.
|
||||||
schema: String,
|
schema: String,
|
||||||
/// The variables denoted in the `schema` should have a variable entry.
|
/// The variables denoted in the `schema` should have a variable entry.
|
||||||
|
@ -339,7 +339,7 @@ impl ModuleRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempt to fetch the configuration for a specific origin.
|
/// Attempt to fetch the configuration for a specific origin.
|
||||||
async fn fetch_config(
|
pub(crate) async fn fetch_config(
|
||||||
&self,
|
&self,
|
||||||
origin: &str,
|
origin: &str,
|
||||||
) -> Result<Vec<RegistryConfiguration>, AnyError> {
|
) -> Result<Vec<RegistryConfiguration>, AnyError> {
|
||||||
|
@ -443,6 +443,11 @@ impl ModuleRegistry {
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
let end = if p.is_some() { i + 1 } else { i };
|
let end = if p.is_some() { i + 1 } else { i };
|
||||||
|
let end = if end > tokens.len() {
|
||||||
|
tokens.len()
|
||||||
|
} else {
|
||||||
|
end
|
||||||
|
};
|
||||||
let compiler = Compiler::new(&tokens[..end], None);
|
let compiler = Compiler::new(&tokens[..end], None);
|
||||||
for (idx, item) in items.into_iter().enumerate() {
|
for (idx, item) in items.into_iter().enumerate() {
|
||||||
let label = if let Some(p) = &p {
|
let label = if let Some(p) = &p {
|
||||||
|
|
|
@ -1839,6 +1839,53 @@ fn lsp_completions_registry_empty() {
|
||||||
shutdown(&mut client);
|
shutdown(&mut client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_auto_discover_registry() {
|
||||||
|
let _g = http_server();
|
||||||
|
let mut client = init("initialize_params.json");
|
||||||
|
did_open(
|
||||||
|
&mut client,
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "import * as a from \"http://localhost:4545/x/a@\""
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request::<_, _, Value>(
|
||||||
|
"textDocument/completion",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 46
|
||||||
|
},
|
||||||
|
"context": {
|
||||||
|
"triggerKind": 2,
|
||||||
|
"triggerCharacter": "@"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert!(maybe_res.is_some());
|
||||||
|
let (method, maybe_res) = client.read_notification().unwrap();
|
||||||
|
assert_eq!(method, "deno/registryState");
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(json!({
|
||||||
|
"origin": "http://localhost:4545",
|
||||||
|
"suggestions": true,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
shutdown(&mut client);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_diagnostics_warn() {
|
fn lsp_diagnostics_warn() {
|
||||||
let _g = http_server();
|
let _g = http_server();
|
||||||
|
|
Loading…
Reference in a new issue