Skip to content

Commit

Permalink
feat(lsp): registry auto discovery (#10813)
Browse files Browse the repository at this point in the history
Closes: #10194
Fixes: #10468
  • Loading branch information
kitsonk authored Jun 1, 2021
1 parent 9abb899 commit bb5bf91
Show file tree
Hide file tree
Showing 8 changed files with 355 additions and 155 deletions.
125 changes: 75 additions & 50 deletions cli/lsp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,69 @@ 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

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
Expand Down Expand Up @@ -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;
}
```
120 changes: 93 additions & 27 deletions cli/lsp/completions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

use super::analysis;
use super::language_server;
use super::lsp_custom;
use super::tsc;

use crate::fs_util::is_supported_ext;
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;
Expand All @@ -34,13 +36,72 @@ 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.
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);
Expand All @@ -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 {
Expand Down Expand Up @@ -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(
&[
Expand All @@ -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()
}]
);
}
}
Loading

0 comments on commit bb5bf91

Please sign in to comment.