settings: Show notification when user/project settings fail to parse (#18122)
Closes #16876 We only ever showed parsing errors, but not if something failed to deserialize. Basically, if you had a stray `,` somewhere, we'd show a notification for user errors, but only squiggly lines if you had a `[]` instead of a `{}`. The squiggly lines would only show up when there were schema errors. In the case of `formatter` settings, for example, if someone put in a `{}` instead of `[]`, we'd never show anything. With this change we always show a notification if parsing user or project settings fails. (Right now, the error message might still be bad, but that's a separate change) Release Notes: - Added a notification to warn users if their user settings or project-local settings failed to deserialize. Demo: https://github.com/user-attachments/assets/e5c48165-f2f7-4b5c-9c6d-6ea74f678683
This commit is contained in:
parent
93730983dd
commit
ace4d5185d
7 changed files with 172 additions and 50 deletions
|
@ -13,7 +13,9 @@ pub use editable_setting_control::*;
|
|||
pub use json_schema::*;
|
||||
pub use keymap_file::KeymapFile;
|
||||
pub use settings_file::*;
|
||||
pub use settings_store::{Settings, SettingsLocation, SettingsSources, SettingsStore};
|
||||
pub use settings_store::{
|
||||
InvalidSettingsError, Settings, SettingsLocation, SettingsSources, SettingsStore,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]
|
||||
pub struct WorktreeId(usize);
|
||||
|
|
|
@ -3,6 +3,7 @@ use collections::{btree_map, hash_map, BTreeMap, HashMap};
|
|||
use fs::Fs;
|
||||
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal};
|
||||
use paths::local_settings_file_relative_path;
|
||||
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
|
||||
use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
@ -10,7 +11,7 @@ use std::{
|
|||
any::{type_name, Any, TypeId},
|
||||
fmt::Debug,
|
||||
ops::Range,
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
str,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
@ -694,9 +695,14 @@ impl SettingsStore {
|
|||
.deserialize_setting(&self.raw_extension_settings)
|
||||
.log_err();
|
||||
|
||||
let user_settings = setting_value
|
||||
.deserialize_setting(&self.raw_user_settings)
|
||||
.log_err();
|
||||
let user_settings = match setting_value.deserialize_setting(&self.raw_user_settings) {
|
||||
Ok(settings) => Some(settings),
|
||||
Err(error) => {
|
||||
return Err(anyhow!(InvalidSettingsError::UserSettings {
|
||||
message: error.to_string()
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
let mut release_channel_settings = None;
|
||||
if let Some(release_settings) = &self
|
||||
|
@ -746,34 +752,43 @@ impl SettingsStore {
|
|||
break;
|
||||
}
|
||||
|
||||
if let Some(local_settings) =
|
||||
setting_value.deserialize_setting(local_settings).log_err()
|
||||
{
|
||||
paths_stack.push(Some((*root_id, path.as_ref())));
|
||||
project_settings_stack.push(local_settings);
|
||||
match setting_value.deserialize_setting(local_settings) {
|
||||
Ok(local_settings) => {
|
||||
paths_stack.push(Some((*root_id, path.as_ref())));
|
||||
project_settings_stack.push(local_settings);
|
||||
|
||||
// If a local settings file changed, then avoid recomputing local
|
||||
// settings for any path outside of that directory.
|
||||
if changed_local_path.map_or(false, |(changed_root_id, changed_local_path)| {
|
||||
*root_id != changed_root_id || !path.starts_with(changed_local_path)
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = setting_value
|
||||
.load_setting(
|
||||
SettingsSources {
|
||||
default: &default_settings,
|
||||
extensions: extension_settings.as_ref(),
|
||||
user: user_settings.as_ref(),
|
||||
release_channel: release_channel_settings.as_ref(),
|
||||
project: &project_settings_stack.iter().collect::<Vec<_>>(),
|
||||
// If a local settings file changed, then avoid recomputing local
|
||||
// settings for any path outside of that directory.
|
||||
if changed_local_path.map_or(
|
||||
false,
|
||||
|(changed_root_id, changed_local_path)| {
|
||||
*root_id != changed_root_id || !path.starts_with(changed_local_path)
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_local_value(*root_id, path.clone(), value);
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(value) = setting_value
|
||||
.load_setting(
|
||||
SettingsSources {
|
||||
default: &default_settings,
|
||||
extensions: extension_settings.as_ref(),
|
||||
user: user_settings.as_ref(),
|
||||
release_channel: release_channel_settings.as_ref(),
|
||||
project: &project_settings_stack.iter().collect::<Vec<_>>(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
.log_err()
|
||||
{
|
||||
setting_value.set_local_value(*root_id, path.clone(), value);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(anyhow!(InvalidSettingsError::LocalSettings {
|
||||
path: path.join(local_settings_file_relative_path()),
|
||||
message: error.to_string()
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -782,6 +797,24 @@ impl SettingsStore {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum InvalidSettingsError {
|
||||
LocalSettings { path: PathBuf, message: String },
|
||||
UserSettings { message: String },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InvalidSettingsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InvalidSettingsError::LocalSettings { message, .. }
|
||||
| InvalidSettingsError::UserSettings { message } => {
|
||||
write!(f, "{}", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for InvalidSettingsError {}
|
||||
|
||||
impl Debug for SettingsStore {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("SettingsStore")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue