mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-25 23:04:27 +00:00
settings_ui: Add maybe settings (#40724)
Closes #ISSUE Adds a `Maybe<T>` type to `settings_content`, that makes the distinction between `null` and omitted settings values explicit. This unlocks a few more settings in the settings UI Release Notes: - N/A *or* Added/Fixed/Improved ...
This commit is contained in:
parent
33bc586ed1
commit
ebaefa8cbc
8 changed files with 434 additions and 31 deletions
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"$schema": "zed://schemas/settings",
|
||||
/// The displayed name of this project. If not set or empty, the root directory name
|
||||
/// The displayed name of this project. If not set or null, the root directory name
|
||||
/// will be displayed.
|
||||
"project_name": "",
|
||||
"project_name": null,
|
||||
// The name of the Zed theme to use for the UI.
|
||||
//
|
||||
// `mode` is one of:
|
||||
|
|
|
|||
|
|
@ -1034,3 +1034,218 @@ impl std::fmt::Display for DelayMs {
|
|||
write!(f, "{}ms", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper type that distinguishes between an explicitly set value (including null) and an unset value.
|
||||
///
|
||||
/// This is useful for configuration where you need to differentiate between:
|
||||
/// - A field that is not present in the configuration file (`Maybe::Unset`)
|
||||
/// - A field that is explicitly set to `null` (`Maybe::Set(None)`)
|
||||
/// - A field that is explicitly set to a value (`Maybe::Set(Some(value))`)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// In JSON:
|
||||
/// - `{}` (field missing) deserializes to `Maybe::Unset`
|
||||
/// - `{"field": null}` deserializes to `Maybe::Set(None)`
|
||||
/// - `{"field": "value"}` deserializes to `Maybe::Set(Some("value"))`
|
||||
///
|
||||
/// WARN: This type should not be wrapped in an option inside of settings, otherwise the default `serde_json` behavior
|
||||
/// of treating `null` and missing as the `Option::None` will be used
|
||||
#[derive(Debug, Clone, PartialEq, Eq, strum::EnumDiscriminants, Default)]
|
||||
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
|
||||
pub enum Maybe<T> {
|
||||
/// An explicitly set value, which may be `None` (representing JSON `null`) or `Some(value)`.
|
||||
Set(Option<T>),
|
||||
/// A value that was not present in the configuration.
|
||||
#[default]
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl<T: Clone> merge_from::MergeFrom for Maybe<T> {
|
||||
fn merge_from(&mut self, other: &Self) {
|
||||
if self.is_unset() {
|
||||
*self = other.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<Option<T>>> for Maybe<T> {
|
||||
fn from(value: Option<Option<T>>) -> Self {
|
||||
match value {
|
||||
Some(value) => Maybe::Set(value),
|
||||
None => Maybe::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Maybe<T> {
|
||||
pub fn is_set(&self) -> bool {
|
||||
matches!(self, Maybe::Set(_))
|
||||
}
|
||||
|
||||
pub fn is_unset(&self) -> bool {
|
||||
matches!(self, Maybe::Unset)
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> Option<T> {
|
||||
match self {
|
||||
Maybe::Set(value) => value,
|
||||
Maybe::Unset => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ref(&self) -> Option<&Option<T>> {
|
||||
match self {
|
||||
Maybe::Set(value) => Some(value),
|
||||
Maybe::Unset => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: serde::Serialize> serde::Serialize for Maybe<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Maybe::Set(value) => value.serialize(serializer),
|
||||
Maybe::Unset => serializer.serialize_none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Maybe<T> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
Option::<T>::deserialize(deserializer).map(Maybe::Set)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: JsonSchema> JsonSchema for Maybe<T> {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
format!("Nullable<{}>", T::schema_name()).into()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema {
|
||||
let mut schema = generator.subschema_for::<Option<T>>();
|
||||
// Add description explaining that null is an explicit value
|
||||
let description = if let Some(existing_desc) =
|
||||
schema.get("description").and_then(|desc| desc.as_str())
|
||||
{
|
||||
format!(
|
||||
"{}. Note: `null` is treated as an explicit value, different from omitting the field entirely.",
|
||||
existing_desc
|
||||
)
|
||||
} else {
|
||||
"This field supports explicit `null` values. Omitting the field is different from setting it to `null`.".to_string()
|
||||
};
|
||||
|
||||
schema.insert("description".to_string(), description.into());
|
||||
|
||||
schema
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json;
|
||||
|
||||
#[test]
|
||||
fn test_maybe() {
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct TestStruct {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Maybe::is_unset")]
|
||||
field: Maybe<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct NumericTest {
|
||||
#[serde(default)]
|
||||
value: Maybe<i32>,
|
||||
}
|
||||
|
||||
let json = "{}";
|
||||
let result: TestStruct = serde_json::from_str(json).unwrap();
|
||||
assert!(result.field.is_unset());
|
||||
assert_eq!(result.field, Maybe::Unset);
|
||||
|
||||
let json = r#"{"field": null}"#;
|
||||
let result: TestStruct = serde_json::from_str(json).unwrap();
|
||||
assert!(result.field.is_set());
|
||||
assert_eq!(result.field, Maybe::Set(None));
|
||||
|
||||
let json = r#"{"field": "hello"}"#;
|
||||
let result: TestStruct = serde_json::from_str(json).unwrap();
|
||||
assert!(result.field.is_set());
|
||||
assert_eq!(result.field, Maybe::Set(Some("hello".to_string())));
|
||||
|
||||
let test = TestStruct {
|
||||
field: Maybe::Unset,
|
||||
};
|
||||
let json = serde_json::to_string(&test).unwrap();
|
||||
assert_eq!(json, "{}");
|
||||
|
||||
let test = TestStruct {
|
||||
field: Maybe::Set(None),
|
||||
};
|
||||
let json = serde_json::to_string(&test).unwrap();
|
||||
assert_eq!(json, r#"{"field":null}"#);
|
||||
|
||||
let test = TestStruct {
|
||||
field: Maybe::Set(Some("world".to_string())),
|
||||
};
|
||||
let json = serde_json::to_string(&test).unwrap();
|
||||
assert_eq!(json, r#"{"field":"world"}"#);
|
||||
|
||||
let default_maybe: Maybe<i32> = Maybe::default();
|
||||
assert!(default_maybe.is_unset());
|
||||
|
||||
let unset: Maybe<String> = Maybe::Unset;
|
||||
assert!(unset.is_unset());
|
||||
assert!(!unset.is_set());
|
||||
|
||||
let set_none: Maybe<String> = Maybe::Set(None);
|
||||
assert!(set_none.is_set());
|
||||
assert!(!set_none.is_unset());
|
||||
|
||||
let set_some: Maybe<String> = Maybe::Set(Some("value".to_string()));
|
||||
assert!(set_some.is_set());
|
||||
assert!(!set_some.is_unset());
|
||||
|
||||
let original = TestStruct {
|
||||
field: Maybe::Set(Some("test".to_string())),
|
||||
};
|
||||
let json = serde_json::to_string(&original).unwrap();
|
||||
let deserialized: TestStruct = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(original, deserialized);
|
||||
|
||||
let json = r#"{"value": 42}"#;
|
||||
let result: NumericTest = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(result.value, Maybe::Set(Some(42)));
|
||||
|
||||
let json = r#"{"value": null}"#;
|
||||
let result: NumericTest = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(result.value, Maybe::Set(None));
|
||||
|
||||
let json = "{}";
|
||||
let result: NumericTest = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(result.value, Maybe::Unset);
|
||||
|
||||
// Test JsonSchema implementation
|
||||
use schemars::schema_for;
|
||||
let schema = schema_for!(Maybe<String>);
|
||||
let schema_json = serde_json::to_value(&schema).unwrap();
|
||||
|
||||
// Verify the description mentions that null is an explicit value
|
||||
let description = schema_json["description"].as_str().unwrap();
|
||||
assert!(
|
||||
description.contains("null") && description.contains("explicit"),
|
||||
"Schema description should mention that null is an explicit value. Got: {}",
|
||||
description
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use settings_macros::MergeFrom;
|
|||
use util::serde::default_true;
|
||||
|
||||
use crate::{
|
||||
AllLanguageSettingsContent, DelayMs, ExtendingVec, ProjectTerminalSettingsContent,
|
||||
AllLanguageSettingsContent, DelayMs, ExtendingVec, Maybe, ProjectTerminalSettingsContent,
|
||||
SlashCommandSettings,
|
||||
};
|
||||
|
||||
|
|
@ -56,11 +56,13 @@ pub struct ProjectSettingsContent {
|
|||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
|
||||
pub struct WorktreeSettingsContent {
|
||||
/// The displayed name of this project. If not set or empty, the root directory name
|
||||
/// The displayed name of this project. If not set or null, the root directory name
|
||||
/// will be displayed.
|
||||
///
|
||||
/// Default: ""
|
||||
pub project_name: Option<String>,
|
||||
/// Default: null
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Maybe::is_unset")]
|
||||
pub project_name: Maybe<String>,
|
||||
|
||||
/// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
|
||||
/// `file_scan_inclusions`.
|
||||
|
|
|
|||
|
|
@ -134,7 +134,19 @@ pub struct TerminalSettingsContent {
|
|||
}
|
||||
|
||||
/// Shell configuration to open the terminal with.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
JsonSchema,
|
||||
MergeFrom,
|
||||
strum::EnumDiscriminants,
|
||||
)]
|
||||
#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Shell {
|
||||
/// Use the system's default terminal configuration in /etc/passwd
|
||||
|
|
|
|||
|
|
@ -853,7 +853,7 @@ impl VsCodeSettings {
|
|||
|
||||
fn worktree_settings_content(&self) -> WorktreeSettingsContent {
|
||||
WorktreeSettingsContent {
|
||||
project_name: None,
|
||||
project_name: crate::Maybe::Unset,
|
||||
file_scan_exclusions: self
|
||||
.read_value("files.watcherExclude")
|
||||
.and_then(|v| v.as_array())
|
||||
|
|
|
|||
|
|
@ -9,6 +9,16 @@ use crate::{
|
|||
SettingsPageItem, SubPageLink, USER, all_language_names, sub_page_stack,
|
||||
};
|
||||
|
||||
const DEFAULT_STRING: String = String::new();
|
||||
/// A default empty string reference. Useful in `pick` functions for cases either in dynamic item fields, or when dealing with `settings::Maybe`
|
||||
/// to avoid the "NO DEFAULT" case.
|
||||
const DEFAULT_EMPTY_STRING: Option<&String> = Some(&DEFAULT_STRING);
|
||||
|
||||
const DEFAULT_SHARED_STRING: SharedString = SharedString::new_static("");
|
||||
/// A default empty string reference. Useful in `pick` functions for cases either in dynamic item fields, or when dealing with `settings::Maybe`
|
||||
/// to avoid the "NO DEFAULT" case.
|
||||
const DEFAULT_EMPTY_SHARED_STRING: Option<&SharedString> = Some(&DEFAULT_SHARED_STRING);
|
||||
|
||||
pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
||||
vec![
|
||||
SettingsPage {
|
||||
|
|
@ -16,16 +26,20 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
|||
items: vec![
|
||||
SettingsPageItem::SectionHeader("General Settings"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Confirm Quit",
|
||||
description: "Confirm before quitting Zed",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| settings_content.workspace.confirm_quit.as_ref(),
|
||||
write: |settings_content, value| {
|
||||
settings_content.workspace.confirm_quit = value;
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
files: USER,
|
||||
files: LOCAL,
|
||||
title: "Project Name",
|
||||
description: "The Displayed Name Of This Project. If Left Empty, The Root Directory Name Will Be Displayed",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
settings_content.project.worktree.project_name.as_ref()?.as_ref().or(DEFAULT_EMPTY_STRING)
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content.project.worktree.project_name = settings::Maybe::Set(value.filter(|name| !name.is_empty()));
|
||||
},
|
||||
}
|
||||
),
|
||||
metadata: Some(Box::new(SettingsFieldMetadata { placeholder: Some("Project Name"), ..Default::default() })),
|
||||
}),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "When Closing With No Tabs",
|
||||
|
|
@ -4205,26 +4219,183 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
|||
title: "Terminal",
|
||||
items: vec![
|
||||
SettingsPageItem::SectionHeader("Environment"),
|
||||
SettingsPageItem::SettingItem(SettingItem {
|
||||
title: "Shell",
|
||||
description: "What shell to use when opening a terminal",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
SettingsPageItem::DynamicItem(DynamicItem {
|
||||
discriminant: SettingItem {
|
||||
files: USER | LOCAL,
|
||||
title: "Shell",
|
||||
description: "What shell to use when opening a terminal",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
settings_content.terminal.as_ref()?.project.shell.as_ref()
|
||||
Some(&dynamic_variants::<settings::Shell>()[
|
||||
settings_content
|
||||
.terminal
|
||||
.as_ref()?
|
||||
.project
|
||||
.shell
|
||||
.as_ref()?
|
||||
.discriminant() as usize])
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
settings_content
|
||||
let Some(value) = value else {
|
||||
return;
|
||||
};
|
||||
let settings_value = settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell = value;
|
||||
.shell
|
||||
.get_or_insert_with(|| settings::Shell::default());
|
||||
*settings_value = match value {
|
||||
settings::ShellDiscriminants::System => {
|
||||
settings::Shell::System
|
||||
},
|
||||
settings::ShellDiscriminants::Program => {
|
||||
let program = match settings_value {
|
||||
settings::Shell::Program(p) => p.clone(),
|
||||
settings::Shell::WithArguments { program, .. } => program.clone(),
|
||||
_ => String::from("sh"),
|
||||
};
|
||||
settings::Shell::Program(program)
|
||||
},
|
||||
settings::ShellDiscriminants::WithArguments => {
|
||||
let (program, args, title_override) = match settings_value {
|
||||
settings::Shell::Program(p) => (p.clone(), vec![], None),
|
||||
settings::Shell::WithArguments { program, args, title_override } => {
|
||||
(program.clone(), args.clone(), title_override.clone())
|
||||
},
|
||||
_ => (String::from("sh"), vec![], None),
|
||||
};
|
||||
settings::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
},
|
||||
pick_discriminant: |settings_content| {
|
||||
Some(settings_content.terminal.as_ref()?.project.shell.as_ref()?.discriminant() as usize)
|
||||
},
|
||||
fields: dynamic_variants::<settings::Shell>().into_iter().map(|variant| {
|
||||
match variant {
|
||||
settings::ShellDiscriminants::System => vec![],
|
||||
settings::ShellDiscriminants::Program => vec![
|
||||
SettingItem {
|
||||
files: USER | LOCAL,
|
||||
title: "Program",
|
||||
description: "The shell program to use",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
match settings_content.terminal.as_ref()?.project.shell.as_ref() {
|
||||
Some(settings::Shell::Program(program)) => Some(program),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
let Some(value) = value else {
|
||||
return;
|
||||
};
|
||||
match settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell.as_mut() {
|
||||
Some(settings::Shell::Program(program)) => *program = value,
|
||||
_ => return
|
||||
}
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
}
|
||||
],
|
||||
settings::ShellDiscriminants::WithArguments => vec![
|
||||
SettingItem {
|
||||
files: USER | LOCAL,
|
||||
title: "Program",
|
||||
description: "The shell program to run",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
match settings_content.terminal.as_ref()?.project.shell.as_ref() {
|
||||
Some(settings::Shell::WithArguments { program, .. }) => Some(program),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
let Some(value) = value else {
|
||||
return;
|
||||
};
|
||||
match settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell.as_mut() {
|
||||
Some(settings::Shell::WithArguments { program, .. }) => *program = value,
|
||||
_ => return
|
||||
}
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
},
|
||||
SettingItem {
|
||||
files: USER | LOCAL,
|
||||
title: "Arguments",
|
||||
description: "The arguments to pass to the shell program",
|
||||
field: Box::new(
|
||||
SettingField {
|
||||
pick: |settings_content| {
|
||||
match settings_content.terminal.as_ref()?.project.shell.as_ref() {
|
||||
Some(settings::Shell::WithArguments { args, .. }) => Some(args),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
let Some(value) = value else {
|
||||
return;
|
||||
};
|
||||
match settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell.as_mut() {
|
||||
Some(settings::Shell::WithArguments { args, .. }) => *args = value,
|
||||
_ => return
|
||||
}
|
||||
},
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
},
|
||||
SettingItem {
|
||||
files: USER | LOCAL,
|
||||
title: "Title Override",
|
||||
description: "An optional string to override the title of the terminal tab",
|
||||
field: Box::new(SettingField {
|
||||
pick: |settings_content| {
|
||||
match settings_content.terminal.as_ref()?.project.shell.as_ref() {
|
||||
Some(settings::Shell::WithArguments { title_override, .. }) => title_override.as_ref().or(DEFAULT_EMPTY_SHARED_STRING),
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
write: |settings_content, value| {
|
||||
match settings_content
|
||||
.terminal
|
||||
.get_or_insert_default()
|
||||
.project
|
||||
.shell.as_mut() {
|
||||
Some(settings::Shell::WithArguments { title_override, .. }) => *title_override = value.filter(|s| !s.is_empty()),
|
||||
_ => return
|
||||
}
|
||||
},
|
||||
}),
|
||||
metadata: None,
|
||||
}
|
||||
],
|
||||
}
|
||||
.unimplemented(),
|
||||
),
|
||||
metadata: None,
|
||||
files: USER | LOCAL,
|
||||
}).collect(),
|
||||
}),
|
||||
SettingsPageItem::DynamicItem(DynamicItem {
|
||||
discriminant: SettingItem {
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ fn init_renderers(cx: &mut App) {
|
|||
})
|
||||
.add_basic_renderer::<bool>(render_toggle_button)
|
||||
.add_basic_renderer::<String>(render_text_field)
|
||||
.add_basic_renderer::<SharedString>(render_text_field)
|
||||
.add_basic_renderer::<settings::SaturatingBool>(render_toggle_button)
|
||||
.add_basic_renderer::<settings::CursorShape>(render_dropdown)
|
||||
.add_basic_renderer::<settings::RestoreOnStartupBehavior>(render_dropdown)
|
||||
|
|
@ -444,8 +445,10 @@ fn init_renderers(cx: &mut App) {
|
|||
.add_basic_renderer::<settings::BufferLineHeightDiscriminants>(render_dropdown)
|
||||
.add_basic_renderer::<settings::AutosaveSettingDiscriminants>(render_dropdown)
|
||||
.add_basic_renderer::<settings::WorkingDirectoryDiscriminants>(render_dropdown)
|
||||
.add_basic_renderer::<settings::MaybeDiscriminants>(render_dropdown)
|
||||
.add_basic_renderer::<settings::IncludeIgnoredContent>(render_dropdown)
|
||||
.add_basic_renderer::<settings::ShowIndentGuides>(render_dropdown)
|
||||
.add_basic_renderer::<settings::ShellDiscriminants>(render_dropdown)
|
||||
// please semicolon stay on next line
|
||||
;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ impl Settings for WorktreeSettings {
|
|||
.collect();
|
||||
|
||||
Self {
|
||||
project_name: worktree.project_name.filter(|p| !p.is_empty()),
|
||||
project_name: worktree.project_name.into_inner(),
|
||||
file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
|
||||
.log_err()
|
||||
.unwrap_or_default(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue