summaryrefslogtreecommitdiff
path: root/cli
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-06-01 21:53:08 +1000
committerGitHub <noreply@github.com>2021-06-01 21:53:08 +1000
commitbb5bf91067e28cef869f5180dc73a9b86e368bdc (patch)
treeb32cf8ec12073f58bb0f5aa2672047485a582843 /cli
parent9abb899f5fd36da65aae78351df3f478f8363700 (diff)
feat(lsp): registry auto discovery (#10813)
Closes: #10194 Fixes: #10468
Diffstat (limited to 'cli')
-rw-r--r--cli/lsp/README.md125
-rw-r--r--cli/lsp/completions.rs120
-rw-r--r--cli/lsp/config.rs60
-rw-r--r--cli/lsp/language_server.rs104
-rw-r--r--cli/lsp/lsp_custom.rs42
-rw-r--r--cli/lsp/mod.rs1
-rw-r--r--cli/lsp/registries.rs11
-rw-r--r--cli/tests/integration_tests_lsp.rs47
8 files changed, 355 insertions, 155 deletions
diff --git a/cli/lsp/README.md b/cli/lsp/README.md
index d480e3133..28ae50c6a 100644
--- a/cli/lsp/README.md
+++ b/cli/lsp/README.md
@@ -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
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
-> complete. This document gives an overview of the structure of the language
-> server.
+> :warning: The Language Server is experimental and not feature complete. This
+> document gives an overview of the structure of the language server.
## 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
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
The LSP currently supports the following custom requests. A client should
@@ -62,55 +115,27 @@ with Deno:
}
```
-## 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`
+## Custom notifications
-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
-}
-```
+There is currently one custom notification that is send from the server to the
+client:
-Would enable Deno with the unstable APIs for this instance of the language
-server.
+- `deno/registryStatus` - when `deno.suggest.imports.autoDiscover` is `true` and
+ an origin for an import being added to a document is not explicitly set in
+ `deno.suggest.imports.hosts`, the origin will be checked and the notification
+ will be sent to the client of the status.
-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.
+ When receiving the notification, if the param `suggestion` is `true`, the
+ 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.
-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.
+ The params for the notification are:
-If the client does not have the `workspaceConfiguration` capability, the
-language server will assume the workspace setting applies to all resources.
+ ```ts
+ interface RegistryStatusNotificationParams {
+ origin: string;
+ suggestions: boolean;
+ }
+ ```
diff --git a/cli/lsp/completions.rs b/cli/lsp/completions.rs
index 56abeb9d2..95a13d59a 100644
--- a/cli/lsp/completions.rs
+++ b/cli/lsp/completions.rs
@@ -2,6 +2,7 @@
use super::analysis;
use super::language_server;
+use super::lsp_custom;
use super::tsc;
use crate::fs_util::is_supported_ext;
@@ -9,6 +10,7 @@ use crate::media_type::MediaType;
use deno_core::normalize_path;
use deno_core::resolve_path;
+use deno_core::resolve_url;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
use deno_core::url::Position;
@@ -34,6 +36,64 @@ pub struct 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
/// completion response, which will be valid import completions for the specific
/// context.
@@ -41,6 +101,7 @@ pub async fn get_import_completions(
specifier: &ModuleSpecifier,
position: &lsp::Position,
state_snapshot: &language_server::StateSnapshot,
+ client: lspower::Client,
) -> Option<lsp::CompletionResponse> {
if let Ok(Some(source)) = state_snapshot.documents.content(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
if !current_specifier.is_empty() {
+ check_auto_config_registry(&current_specifier, state_snapshot, client)
+ .await;
let offset = if position.character > range.start.character {
(position.character - range.start.character) as usize
} else {
@@ -808,11 +871,17 @@ mod tests {
}
#[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 position = lsp::Position {
- line: 0,
- character: 21,
+ let range = lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 20,
+ },
+ end: lsp::Position {
+ line: 0,
+ character: 21,
+ },
};
let state_snapshot = setup(
&[
@@ -822,32 +891,29 @@ mod tests {
&[("https://deno.land/x/a/b/c.ts", "console.log(1);\n")],
);
let actual =
- get_import_completions(&specifier, &position, &state_snapshot).await;
+ get_workspace_completions(&specifier, "h", &range, &state_snapshot);
assert_eq!(
actual,
- Some(lsp::CompletionResponse::List(lsp::CompletionList {
- is_incomplete: false,
- items: vec![lsp::CompletionItem {
- label: "https://deno.land/x/a/b/c.ts".to_string(),
- kind: Some(lsp::CompletionItemKind::File),
- detail: Some("(remote)".to_string()),
- sort_text: Some("1".to_string()),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- range: lsp::Range {
- start: lsp::Position {
- line: 0,
- character: 20
- },
- end: lsp::Position {
- line: 0,
- character: 21,
- }
+ vec![lsp::CompletionItem {
+ label: "https://deno.land/x/a/b/c.ts".to_string(),
+ kind: Some(lsp::CompletionItemKind::File),
+ detail: Some("(remote)".to_string()),
+ sort_text: Some("1".to_string()),
+ text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+ range: lsp::Range {
+ start: lsp::Position {
+ line: 0,
+ character: 20
},
- new_text: "https://deno.land/x/a/b/c.ts".to_string(),
- })),
- ..Default::default()
- }]
- }))
+ end: lsp::Position {
+ line: 0,
+ character: 21,
+ }
+ },
+ new_text: "https://deno.land/x/a/b/c.ts".to_string(),
+ })),
+ ..Default::default()
+ }]
);
}
}
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 33f9122da..e46e8dd52 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -28,7 +28,7 @@ pub struct ClientCapabilities {
pub line_folding_only: bool,
}
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensSettings {
/// 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")]
pub struct CompletionSettings {
#[serde(default)]
pub complete_function_calls: bool,
- #[serde(default)]
+ #[serde(default = "is_true")]
pub names: bool,
- #[serde(default)]
+ #[serde(default = "is_true")]
pub paths: bool,
- #[serde(default)]
+ #[serde(default = "is_true")]
pub auto_imports: bool,
#[serde(default)]
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")]
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)]
pub hosts: HashMap<String, bool>,
}
@@ -90,6 +100,7 @@ pub struct ImportCompletionSettings {
impl Default for ImportCompletionSettings {
fn default() -> Self {
Self {
+ auto_discover: true,
hosts: HashMap::default(),
}
}
@@ -104,10 +115,11 @@ pub struct SpecifierSettings {
}
/// 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")]
pub struct WorkspaceSettings {
/// A flag that indicates if Deno is enabled for the workspace.
+ #[serde(default)]
pub enable: bool,
/// 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");
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,
+ }
+ );
+ }
}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index c8b959596..3a751e319 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -3,8 +3,6 @@
use deno_core::error::anyhow;
use deno_core::error::AnyError;
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::json;
use deno_core::serde_json::Value;
@@ -44,6 +42,7 @@ use super::config::SETTINGS_SECTION;
use super::diagnostics;
use super::diagnostics::DiagnosticSource;
use super::documents::DocumentCache;
+use super::lsp_custom;
use super::performance::Performance;
use super::registries;
use super::sources;
@@ -385,10 +384,9 @@ impl Inner {
.iter()
{
if *enabled {
- info!("Enabling auto complete registry for: {}", registry);
+ info!("Enabling import suggestions for: {}", registry);
self.module_registries.enable(registry).await?;
} else {
- info!("Disabling auto complete registry for: {}", registry);
self.module_registries.disable(registry).await?;
}
}
@@ -552,17 +550,11 @@ impl Inner {
async fn initialized(&mut self, _: InitializedParams) {
// Check to see if we need to setup the import map
if let Err(err) = self.update_import_map().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
// Check to see if we need to setup any module registries
if let Err(err) = self.update_registries().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
if self
@@ -713,22 +705,13 @@ impl Inner {
self.update_debug_flag();
if let Err(err) = self.update_import_map().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
if let Err(err) = self.update_registries().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
if let Err(err) = self.update_tsconfig().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
if let Err(err) = self.diagnostics_server.update() {
error!("{}", err);
@@ -748,10 +731,7 @@ impl Inner {
if let Some(import_map_uri) = &self.maybe_import_map_uri {
if params.changes.iter().any(|fe| *import_map_uri == fe.uri) {
if let Err(err) = self.update_import_map().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
}
}
@@ -759,10 +739,7 @@ impl Inner {
if let Some(config_uri) = &self.maybe_config_uri {
if params.changes.iter().any(|fe| *config_uri == fe.uri) {
if let Err(err) = self.update_tsconfig().await {
- self
- .client
- .show_message(MessageType::Warning, err.to_string())
- .await;
+ self.client.show_message(MessageType::Warning, err).await;
}
}
}
@@ -1549,6 +1526,7 @@ impl Inner {
&specifier,
&params.text_document_position.position,
&self.snapshot()?,
+ self.client.clone(),
)
.await
{
@@ -2004,27 +1982,31 @@ impl Inner {
params: Option<Value>,
) -> LspResult<Option<Value>> {
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(Err(err)) => Err(LspError::invalid_params(err.to_string())),
None => Err(LspError::invalid_params("Missing parameters")),
},
- "deno/performance" => Ok(Some(self.get_performance())),
- "deno/reloadImportRegistries" => self.reload_import_registries().await,
- "deno/virtualTextDocument" => match params.map(serde_json::from_value) {
- Some(Ok(params)) => Ok(Some(
- serde_json::to_value(self.virtual_text_document(params).await?)
- .map_err(|err| {
- error!(
- "Failed to serialize virtual_text_document response: {}",
- err
- );
- LspError::internal_error()
- })?,
- )),
- Some(Err(err)) => Err(LspError::invalid_params(err.to_string())),
- None => Err(LspError::invalid_params("Missing parameters")),
- },
+ lsp_custom::PERFORMANCE_REQUEST => Ok(Some(self.get_performance())),
+ lsp_custom::RELOAD_IMPORT_REGISTRIES_REQUEST => {
+ self.reload_import_registries().await
+ }
+ lsp_custom::VIRTUAL_TEXT_DOCUMENT => {
+ match params.map(serde_json::from_value) {
+ Some(Ok(params)) => Ok(Some(
+ serde_json::to_value(self.virtual_text_document(params).await?)
+ .map_err(|err| {
+ error!(
+ "Failed to serialize virtual_text_document response: {}",
+ err
+ );
+ LspError::internal_error()
+ })?,
+ )),
+ 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);
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
impl Inner {
/// Similar to `deno cache` on the command line, where modules will be cached
/// 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(&params));
let referrer = self.url_map.normalize_url(&params.referrer.uri);
if !params.uris.is_empty() {
@@ -2519,7 +2487,7 @@ impl Inner {
async fn virtual_text_document(
&mut self,
- params: VirtualTextDocumentParams,
+ params: lsp_custom::VirtualTextDocumentParams,
) -> LspResult<Option<String>> {
let mark = self
.performance
diff --git a/cli/lsp/lsp_custom.rs b/cli/lsp/lsp_custom.rs
new file mode 100644
index 000000000..c543a6c1b
--- /dev/null
+++ b/cli/lsp/lsp_custom.rs
@@ -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,
+}
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index 9d9c1ff86..488507a5b 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -11,6 +11,7 @@ mod config;
mod diagnostics;
mod documents;
pub(crate) mod language_server;
+mod lsp_custom;
mod path_to_regex;
mod performance;
mod registries;
diff --git a/cli/lsp/registries.rs b/cli/lsp/registries.rs
index 877f03be3..29ec3258b 100644
--- a/cli/lsp/registries.rs
+++ b/cli/lsp/registries.rs
@@ -219,7 +219,7 @@ fn validate_config(config: &RegistryConfigurationJson) -> Result<(), AnyError> {
}
#[derive(Debug, Clone, Deserialize)]
-struct RegistryConfigurationVariable {
+pub(crate) struct RegistryConfigurationVariable {
/// The name of the variable.
key: String,
/// The URL with variable substitutions of the endpoint that will provide
@@ -228,7 +228,7 @@ struct RegistryConfigurationVariable {
}
#[derive(Debug, Clone, Deserialize)]
-struct RegistryConfiguration {
+pub(crate) struct RegistryConfiguration {
/// A Express-like path which describes how URLs are composed for a registry.
schema: String,
/// 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.
- async fn fetch_config(
+ pub(crate) async fn fetch_config(
&self,
origin: &str,
) -> Result<Vec<RegistryConfiguration>, AnyError> {
@@ -443,6 +443,11 @@ impl ModuleRegistry {
.await
{
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);
for (idx, item) in items.into_iter().enumerate() {
let label = if let Some(p) = &p {
diff --git a/cli/tests/integration_tests_lsp.rs b/cli/tests/integration_tests_lsp.rs
index 999d2de71..e4b963f2b 100644
--- a/cli/tests/integration_tests_lsp.rs
+++ b/cli/tests/integration_tests_lsp.rs
@@ -1840,6 +1840,53 @@ fn lsp_completions_registry_empty() {
}
#[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]
fn lsp_diagnostics_warn() {
let _g = http_server();
let mut client = init("initialize_params.json");