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:
Thorsten Ball 2024-09-20 10:53:06 +02:00 committed by GitHub
parent 93730983dd
commit ace4d5185d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 172 additions and 50 deletions

View file

@ -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);

View file

@ -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")