summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/lsp/client.rs35
-rw-r--r--cli/lsp/language_server.rs120
-rw-r--r--cli/lsp/lsp_custom.rs43
-rw-r--r--cli/lsp/mod.rs5
-rw-r--r--cli/tests/integration/lsp_tests.rs137
5 files changed, 318 insertions, 22 deletions
diff --git a/cli/lsp/client.rs b/cli/lsp/client.rs
index b5cdf8eb9..210aa1da3 100644
--- a/cli/lsp/client.rs
+++ b/cli/lsp/client.rs
@@ -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>,
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 485eef938..2a36708fb 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -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(&params));
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;
}
- 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()) {
- if let Err(err) = self.update_package_json() {
- 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 {
+ 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(
diff --git a/cli/lsp/lsp_custom.rs b/cli/lsp/lsp_custom.rs
index d9dbae8a4..685da6b14 100644
--- a/cli/lsp/lsp_custom.rs
+++ b/cli/lsp/lsp_custom.rs
@@ -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";
@@ -25,6 +25,16 @@ pub struct CacheParams {
}
#[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,
pub suggestions: bool,
@@ -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 {}
diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs
index c2f3eda71..af6ef88c9 100644
--- a/cli/lsp/mod.rs
+++ b/cli/lsp/mod.rs
@@ -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,
diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs
index c13053db8..b5af78b11 100644
--- a/cli/tests/integration/lsp_tests.rs
+++ b/cli/tests/integration/lsp_tests.rs
@@ -838,6 +838,137 @@ fn lsp_workspace_enable_paths_no_workspace_configuration() {
}
#[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();
let temp_dir = context.temp_dir();
@@ -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(),
}
])
);