1
0
Fork 0
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:
Kitson Kelly 2021-06-01 21:53:08 +10:00 committed by GitHub
parent 9abb899f5f
commit bb5bf91067
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 355 additions and 155 deletions

View file

@ -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.

View file

@ -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(&current_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()
}]
); );
} }
} }

View file

@ -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,
}
);
}
} }

View file

@ -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,
&params.text_document_position.position, &params.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(&params)); let mark = self.performance.mark("cache", Some(&params));
let referrer = self.url_map.normalize_url(&params.referrer.uri); let referrer = self.url_map.normalize_url(&params.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
View 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,
}

View file

@ -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;

View file

@ -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 {

View file

@ -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();