mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -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
|
||||
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
|
||||
## 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.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`
|
||||
- `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.
|
||||
|
||||
There are settings that are support on a per resource basis by the language
|
||||
server:
|
||||
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.
|
||||
|
||||
- `deno.enable`
|
||||
The params for the notification are:
|
||||
|
||||
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.
|
||||
```ts
|
||||
interface RegistryStatusNotificationParams {
|
||||
origin: string;
|
||||
suggestions: boolean;
|
||||
}
|
||||
```
|
||||
|
|
|
@ -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(¤t_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()
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
¶ms.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(¶ms));
|
||||
let referrer = self.url_map.normalize_url(¶ms.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
|
||||
|
|
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 documents;
|
||||
pub(crate) mod language_server;
|
||||
mod lsp_custom;
|
||||
mod path_to_regex;
|
||||
mod performance;
|
||||
mod registries;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -1839,6 +1839,53 @@ fn lsp_completions_registry_empty() {
|
|||
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]
|
||||
fn lsp_diagnostics_warn() {
|
||||
let _g = http_server();
|
||||
|
|
Loading…
Reference in a new issue