1
0
Fork 0
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:
Nayeem Rahman 2023-10-12 16:07:27 +01:00 committed by GitHub
parent eaeb10cee1
commit 5dd010a4fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 318 additions and 22 deletions

View file

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

View file

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

View file

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

View file

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

View file

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