mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(lsp): send "deno/didChangeDenoConfiguration" notifications (#20827)
This commit is contained in:
parent
eaeb10cee1
commit
5dd010a4fb
5 changed files with 318 additions and 22 deletions
|
@ -84,6 +84,19 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn send_did_change_deno_configuration_notification(
|
||||
&self,
|
||||
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
|
||||
) {
|
||||
// do on a task in case the caller currently is in the lsp lock
|
||||
let client = self.0.clone();
|
||||
spawn(async move {
|
||||
client
|
||||
.send_did_change_deno_configuration_notification(params)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_message(
|
||||
&self,
|
||||
message_type: lsp::MessageType,
|
||||
|
@ -184,6 +197,10 @@ trait ClientTrait: Send + Sync {
|
|||
params: lsp_custom::DiagnosticBatchNotificationParams,
|
||||
);
|
||||
async fn send_test_notification(&self, params: TestingNotification);
|
||||
async fn send_did_change_deno_configuration_notification(
|
||||
&self,
|
||||
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
|
||||
);
|
||||
async fn specifier_configurations(
|
||||
&self,
|
||||
uris: Vec<lsp::Url>,
|
||||
|
@ -259,6 +276,18 @@ impl ClientTrait for TowerClient {
|
|||
}
|
||||
}
|
||||
|
||||
async fn send_did_change_deno_configuration_notification(
|
||||
&self,
|
||||
params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
|
||||
) {
|
||||
self
|
||||
.0
|
||||
.send_notification::<lsp_custom::DidChangeDenoConfigurationNotification>(
|
||||
params,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn specifier_configurations(
|
||||
&self,
|
||||
uris: Vec<lsp::Url>,
|
||||
|
@ -371,6 +400,12 @@ impl ClientTrait for ReplClient {
|
|||
|
||||
async fn send_test_notification(&self, _params: TestingNotification) {}
|
||||
|
||||
async fn send_did_change_deno_configuration_notification(
|
||||
&self,
|
||||
_params: lsp_custom::DidChangeDenoConfigurationNotificationParams,
|
||||
) {
|
||||
}
|
||||
|
||||
async fn specifier_configurations(
|
||||
&self,
|
||||
uris: Vec<lsp::Url>,
|
||||
|
|
|
@ -20,6 +20,7 @@ use deno_runtime::deno_node::PackageJson;
|
|||
use deno_runtime::deno_tls::rustls::RootCertStore;
|
||||
use deno_runtime::deno_tls::RootCertStoreProvider;
|
||||
use import_map::ImportMap;
|
||||
use indexmap::IndexSet;
|
||||
use log::error;
|
||||
use serde_json::from_value;
|
||||
use std::collections::HashMap;
|
||||
|
@ -64,6 +65,7 @@ use super::documents::UpdateDocumentConfigOptions;
|
|||
use super::logging::lsp_log;
|
||||
use super::logging::lsp_warn;
|
||||
use super::lsp_custom;
|
||||
use super::lsp_custom::TaskDefinition;
|
||||
use super::npm::CliNpmSearchApi;
|
||||
use super::parent_process_checker;
|
||||
use super::performance::Performance;
|
||||
|
@ -343,8 +345,8 @@ impl LanguageServer {
|
|||
self.0.write().await.reload_import_registries().await
|
||||
}
|
||||
|
||||
pub async fn task_request(&self) -> LspResult<Option<Value>> {
|
||||
self.0.read().await.get_tasks()
|
||||
pub async fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> {
|
||||
self.0.read().await.task_definitions()
|
||||
}
|
||||
|
||||
pub async fn test_run_request(
|
||||
|
@ -1457,7 +1459,7 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
fn has_config_changed(config: &Config, changes: &HashSet<Url>) -> bool {
|
||||
fn has_config_changed(config: &Config, changes: &IndexSet<Url>) -> bool {
|
||||
// Check the canonicalized specifier here because file watcher
|
||||
// changes will be for the canonicalized path in vscode, but also check the
|
||||
// non-canonicalized specifier in order to please the tests and handle
|
||||
|
@ -1508,31 +1510,91 @@ impl Inner {
|
|||
.performance
|
||||
.mark("did_change_watched_files", Some(¶ms));
|
||||
let mut touched = false;
|
||||
let changes: HashSet<Url> = params
|
||||
let changes: IndexSet<Url> = params
|
||||
.changes
|
||||
.iter()
|
||||
.map(|f| self.url_map.normalize_url(&f.uri, LspUrlKind::File))
|
||||
.collect();
|
||||
|
||||
let mut config_changes = IndexSet::with_capacity(changes.len());
|
||||
|
||||
// if the current deno.json has changed, we need to reload it
|
||||
if has_config_changed(&self.config, &changes) {
|
||||
// Check the 'current' config specifier from both before and after it's
|
||||
// updated. Check canonicalized and uncanonicalized variants for each.
|
||||
// If any are included in `changes`, send our custom notification for
|
||||
// `deno.json` changes: `deno/didChangeDenoConfigurationNotification`.
|
||||
let mut files_to_check = IndexSet::with_capacity(4);
|
||||
// Collect previous config specifiers.
|
||||
if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) {
|
||||
files_to_check.insert(url.clone());
|
||||
}
|
||||
if let Some(url) = self.config.maybe_config_file_canonicalized_specifier()
|
||||
{
|
||||
files_to_check.insert(url.clone());
|
||||
}
|
||||
// Update config.
|
||||
if let Err(err) = self.update_config_file().await {
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
// Collect new config specifiers.
|
||||
if let Some(url) = self.config.maybe_config_file().map(|c| &c.specifier) {
|
||||
files_to_check.insert(url.clone());
|
||||
}
|
||||
if let Some(url) = self.config.maybe_config_file_canonicalized_specifier()
|
||||
{
|
||||
files_to_check.insert(url.clone());
|
||||
}
|
||||
config_changes.extend(
|
||||
params
|
||||
.changes
|
||||
.iter()
|
||||
.filter(|e| files_to_check.contains(&e.uri))
|
||||
.map(|e| lsp_custom::DenoConfigurationChangeEvent {
|
||||
file_event: e.clone(),
|
||||
configuration_type: lsp_custom::DenoConfigurationType::DenoJson,
|
||||
}),
|
||||
);
|
||||
if let Err(err) = self.update_tsconfig().await {
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
touched = true;
|
||||
}
|
||||
|
||||
let has_package_json_changed = changes
|
||||
.iter()
|
||||
.any(|e| e.as_str().ends_with("/package.json"));
|
||||
|
||||
if has_package_json_changed {
|
||||
let mut files_to_check = IndexSet::with_capacity(2);
|
||||
if let Some(package_json) = &self.maybe_package_json {
|
||||
// always update the package json if the deno config changes
|
||||
if touched || changes.contains(&package_json.specifier()) {
|
||||
files_to_check.insert(package_json.specifier());
|
||||
}
|
||||
if let Err(err) = self.update_package_json() {
|
||||
self.client.show_message(MessageType::WARNING, err);
|
||||
}
|
||||
if let Some(package_json) = &self.maybe_package_json {
|
||||
files_to_check.insert(package_json.specifier());
|
||||
}
|
||||
config_changes.extend(
|
||||
params
|
||||
.changes
|
||||
.iter()
|
||||
.filter(|e| files_to_check.contains(&e.uri))
|
||||
.map(|e| lsp_custom::DenoConfigurationChangeEvent {
|
||||
file_event: e.clone(),
|
||||
configuration_type: lsp_custom::DenoConfigurationType::PackageJson,
|
||||
}),
|
||||
);
|
||||
touched = true;
|
||||
}
|
||||
|
||||
if !config_changes.is_empty() {
|
||||
self.client.send_did_change_deno_configuration_notification(
|
||||
lsp_custom::DidChangeDenoConfigurationNotificationParams {
|
||||
changes: config_changes.into_iter().collect(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// if the current import map, or config file has changed, we need to
|
||||
|
@ -3580,13 +3642,35 @@ impl Inner {
|
|||
json!({ "averages": averages })
|
||||
}
|
||||
|
||||
fn get_tasks(&self) -> LspResult<Option<Value>> {
|
||||
Ok(
|
||||
self
|
||||
.config
|
||||
.maybe_config_file()
|
||||
.and_then(|cf| cf.to_lsp_tasks()),
|
||||
)
|
||||
fn task_definitions(&self) -> LspResult<Vec<TaskDefinition>> {
|
||||
let mut result = vec![];
|
||||
if let Some(config_file) = self.config.maybe_config_file() {
|
||||
if let Some(tasks) = json!(&config_file.json.tasks).as_object() {
|
||||
for (name, value) in tasks {
|
||||
let Some(command) = value.as_str() else {
|
||||
continue;
|
||||
};
|
||||
result.push(TaskDefinition {
|
||||
name: name.clone(),
|
||||
command: command.to_string(),
|
||||
source_uri: config_file.specifier.clone(),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
if let Some(package_json) = &self.maybe_package_json {
|
||||
if let Some(scripts) = &package_json.scripts {
|
||||
for (name, command) in scripts {
|
||||
result.push(TaskDefinition {
|
||||
name: name.clone(),
|
||||
command: command.clone(),
|
||||
source_uri: package_json.specifier(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
result.sort_by_key(|d| d.name.clone());
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn inlay_hint(
|
||||
|
|
|
@ -6,7 +6,7 @@ use tower_lsp::lsp_types as lsp;
|
|||
|
||||
pub const CACHE_REQUEST: &str = "deno/cache";
|
||||
pub const PERFORMANCE_REQUEST: &str = "deno/performance";
|
||||
pub const TASK_REQUEST: &str = "deno/task";
|
||||
pub const TASK_REQUEST: &str = "deno/taskDefinitions";
|
||||
pub const RELOAD_IMPORT_REGISTRIES_REQUEST: &str =
|
||||
"deno/reloadImportRegistries";
|
||||
pub const VIRTUAL_TEXT_DOCUMENT: &str = "deno/virtualTextDocument";
|
||||
|
@ -24,6 +24,16 @@ pub struct CacheParams {
|
|||
pub uris: Vec<lsp::TextDocumentIdentifier>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TaskDefinition {
|
||||
pub name: String,
|
||||
// TODO(nayeemrmn): Rename this to `command` in vscode_deno.
|
||||
#[serde(rename = "detail")]
|
||||
pub command: String,
|
||||
pub source_uri: lsp::Url,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct RegistryStateNotificationParams {
|
||||
pub origin: String,
|
||||
|
@ -50,6 +60,37 @@ pub struct DiagnosticBatchNotificationParams {
|
|||
pub messages_len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Copy, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum DenoConfigurationType {
|
||||
DenoJson,
|
||||
PackageJson,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DenoConfigurationChangeEvent {
|
||||
#[serde(flatten)]
|
||||
pub file_event: lsp::FileEvent,
|
||||
pub configuration_type: DenoConfigurationType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct DidChangeDenoConfigurationNotificationParams {
|
||||
pub changes: Vec<DenoConfigurationChangeEvent>,
|
||||
}
|
||||
|
||||
pub enum DidChangeDenoConfigurationNotification {}
|
||||
|
||||
impl lsp::notification::Notification
|
||||
for DidChangeDenoConfigurationNotification
|
||||
{
|
||||
type Params = DidChangeDenoConfigurationNotificationParams;
|
||||
|
||||
const METHOD: &'static str = "deno/didChangeDenoConfiguration";
|
||||
}
|
||||
|
||||
/// This notification is only sent for testing purposes
|
||||
/// in order to know what the latest diagnostics are.
|
||||
pub enum DiagnosticBatchNotification {}
|
||||
|
|
|
@ -56,7 +56,10 @@ pub async fn start() -> Result<(), AnyError> {
|
|||
lsp_custom::RELOAD_IMPORT_REGISTRIES_REQUEST,
|
||||
LanguageServer::reload_import_registries_request,
|
||||
)
|
||||
.custom_method(lsp_custom::TASK_REQUEST, LanguageServer::task_request)
|
||||
.custom_method(lsp_custom::TASK_REQUEST, LanguageServer::task_definitions)
|
||||
// TODO(nayeemrmn): Rename this to `deno/taskDefinitions` in vscode_deno and
|
||||
// remove this alias.
|
||||
.custom_method("deno/task", LanguageServer::task_definitions)
|
||||
.custom_method(testing::TEST_RUN_REQUEST, LanguageServer::test_run_request)
|
||||
.custom_method(
|
||||
testing::TEST_RUN_CANCEL_REQUEST,
|
||||
|
|
|
@ -837,6 +837,137 @@ fn lsp_workspace_enable_paths_no_workspace_configuration() {
|
|||
client.shutdown();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_did_change_deno_configuration_notification() {
|
||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||
let temp_dir = context.temp_dir();
|
||||
let mut client = context.new_lsp_command().build();
|
||||
client.initialize_default();
|
||||
|
||||
temp_dir.write("deno.json", json!({}).to_string());
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 1,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 1,
|
||||
"configurationType": "denoJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
|
||||
temp_dir.write(
|
||||
"deno.json",
|
||||
json!({ "fmt": { "semiColons": false } }).to_string(),
|
||||
);
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 2,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 2,
|
||||
"configurationType": "denoJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
|
||||
temp_dir.remove_file("deno.json");
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 3,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("deno.json").unwrap(),
|
||||
"type": 3,
|
||||
"configurationType": "denoJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
|
||||
temp_dir.write("package.json", json!({}).to_string());
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 1,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 1,
|
||||
"configurationType": "packageJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
|
||||
temp_dir.write("package.json", json!({ "type": "module" }).to_string());
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 2,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 2,
|
||||
"configurationType": "packageJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
|
||||
temp_dir.remove_file("package.json");
|
||||
client.did_change_watched_files(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 3,
|
||||
}],
|
||||
}));
|
||||
let res = client
|
||||
.read_notification_with_method::<Value>("deno/didChangeDenoConfiguration");
|
||||
assert_eq!(
|
||||
res,
|
||||
Some(json!({
|
||||
"changes": [{
|
||||
"uri": temp_dir.uri().join("package.json").unwrap(),
|
||||
"type": 3,
|
||||
"configurationType": "packageJson"
|
||||
}],
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lsp_deno_task() {
|
||||
let context = TestContextBuilder::new().use_temp_cwd().build();
|
||||
|
@ -863,10 +994,12 @@ fn lsp_deno_task() {
|
|||
json!([
|
||||
{
|
||||
"name": "build",
|
||||
"detail": "deno test"
|
||||
"detail": "deno test",
|
||||
"sourceUri": temp_dir.uri().join("deno.jsonc").unwrap(),
|
||||
}, {
|
||||
"name": "some:test",
|
||||
"detail": "deno bundle mod.ts"
|
||||
"detail": "deno bundle mod.ts",
|
||||
"sourceUri": temp_dir.uri().join("deno.jsonc").unwrap(),
|
||||
}
|
||||
])
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue