Settings refactor (#38367)
Co-Authored-By: Ben K <ben@zed.dev> Co-Authored-By: Anthony <anthony@zed.dev> Co-Authored-By: Mikayla <mikayla@zed.dev> Release Notes: - settings: Major internal changes to settings. The primary user-facing effect is that some settings which did not make sense in project settings files are no-longer read from there. (For example the inline blame settings) --------- Co-authored-by: Ben Kunkle <ben@zed.dev> Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com> Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
co-authored by
Ben Kunkle
Mikayla Maki
Anthony
parent
0a9023bce0
commit
fcdab160f9
@@ -2,22 +2,18 @@ use alacritty_terminal::vte::ansi::{
|
||||
CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
|
||||
};
|
||||
use collections::HashMap;
|
||||
use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
|
||||
use gpui::{App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use settings::{SettingsKey, SettingsSources, SettingsUi};
|
||||
use std::path::PathBuf;
|
||||
pub use settings::AlternateScroll;
|
||||
use settings::{
|
||||
CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition,
|
||||
TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory,
|
||||
};
|
||||
use task::Shell;
|
||||
use theme::FontFamilyName;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TerminalDockPosition {
|
||||
Left,
|
||||
Bottom,
|
||||
Right,
|
||||
}
|
||||
use util::MergeFrom;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct Toolbar {
|
||||
@@ -28,7 +24,7 @@ pub struct Toolbar {
|
||||
pub struct TerminalSettings {
|
||||
pub shell: Shell,
|
||||
pub working_directory: WorkingDirectory,
|
||||
pub font_size: Option<Pixels>,
|
||||
pub font_size: Option<Pixels>, // todo(settings_refactor) can be non-optional...
|
||||
pub font_family: Option<FontFamilyName>,
|
||||
pub font_fallbacks: Option<FontFallbacks>,
|
||||
pub font_features: Option<FontFeatures>,
|
||||
@@ -60,218 +56,135 @@ pub struct ScrollbarSettings {
|
||||
pub show: Option<ShowScrollbar>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ScrollbarSettingsContent {
|
||||
/// When to show the scrollbar in the terminal.
|
||||
///
|
||||
/// Default: inherits editor scrollbar settings
|
||||
pub show: Option<Option<ShowScrollbar>>,
|
||||
}
|
||||
|
||||
/// When to show the scrollbar in the terminal.
|
||||
///
|
||||
/// Default: auto
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ShowScrollbar {
|
||||
/// Show the scrollbar if there's important information or
|
||||
/// follow the system's configured behavior.
|
||||
Auto,
|
||||
/// Match the system's configured behavior.
|
||||
System,
|
||||
/// Always show the scrollbar.
|
||||
Always,
|
||||
/// Never show the scrollbar.
|
||||
Never,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VenvSettings {
|
||||
#[default]
|
||||
Off,
|
||||
On {
|
||||
/// Default directories to search for virtual environments, relative
|
||||
/// to the current working directory. We recommend overriding this
|
||||
/// in your project's settings, rather than globally.
|
||||
activate_script: Option<ActivateScript>,
|
||||
venv_name: Option<String>,
|
||||
directories: Option<Vec<PathBuf>>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct VenvSettingsContent<'a> {
|
||||
pub activate_script: ActivateScript,
|
||||
pub venv_name: &'a str,
|
||||
pub directories: &'a [PathBuf],
|
||||
}
|
||||
|
||||
impl VenvSettings {
|
||||
pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
|
||||
match self {
|
||||
VenvSettings::Off => None,
|
||||
VenvSettings::On {
|
||||
activate_script,
|
||||
venv_name,
|
||||
directories,
|
||||
} => Some(VenvSettingsContent {
|
||||
activate_script: activate_script.unwrap_or(ActivateScript::Default),
|
||||
venv_name: venv_name.as_deref().unwrap_or(""),
|
||||
directories: directories.as_deref().unwrap_or(&[]),
|
||||
}),
|
||||
}
|
||||
fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell {
|
||||
match shell {
|
||||
settings::Shell::System => Shell::System,
|
||||
settings::Shell::Program(program) => Shell::Program(program),
|
||||
settings::Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
} => Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ActivateScript {
|
||||
#[default]
|
||||
Default,
|
||||
Csh,
|
||||
Fish,
|
||||
Nushell,
|
||||
PowerShell,
|
||||
Pyenv,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
|
||||
#[settings_key(key = "terminal")]
|
||||
pub struct TerminalSettingsContent {
|
||||
/// What shell to use when opening a terminal.
|
||||
///
|
||||
/// Default: system
|
||||
pub shell: Option<Shell>,
|
||||
/// What working directory to use when launching the terminal
|
||||
///
|
||||
/// Default: current_project_directory
|
||||
pub working_directory: Option<WorkingDirectory>,
|
||||
/// Sets the terminal's font size.
|
||||
///
|
||||
/// If this option is not included,
|
||||
/// the terminal will default to matching the buffer's font size.
|
||||
pub font_size: Option<f32>,
|
||||
/// Sets the terminal's font family.
|
||||
///
|
||||
/// If this option is not included,
|
||||
/// the terminal will default to matching the buffer's font family.
|
||||
pub font_family: Option<FontFamilyName>,
|
||||
|
||||
/// Sets the terminal's font fallbacks.
|
||||
///
|
||||
/// If this option is not included,
|
||||
/// the terminal will default to matching the buffer's font fallbacks.
|
||||
#[schemars(extend("uniqueItems" = true))]
|
||||
pub font_fallbacks: Option<Vec<FontFamilyName>>,
|
||||
|
||||
/// Sets the terminal's line height.
|
||||
///
|
||||
/// Default: comfortable
|
||||
pub line_height: Option<TerminalLineHeight>,
|
||||
pub font_features: Option<FontFeatures>,
|
||||
/// Sets the terminal's font weight in CSS weight units 0-900.
|
||||
pub font_weight: Option<f32>,
|
||||
/// Any key-value pairs added to this list will be added to the terminal's
|
||||
/// environment. Use `:` to separate multiple values.
|
||||
///
|
||||
/// Default: {}
|
||||
pub env: Option<HashMap<String, String>>,
|
||||
/// Default cursor shape for the terminal.
|
||||
/// Can be "bar", "block", "underline", or "hollow".
|
||||
///
|
||||
/// Default: None
|
||||
pub cursor_shape: Option<CursorShape>,
|
||||
/// Sets the cursor blinking behavior in the terminal.
|
||||
///
|
||||
/// Default: terminal_controlled
|
||||
pub blinking: Option<TerminalBlink>,
|
||||
/// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
|
||||
/// Alternate Scroll mode converts mouse scroll events into up / down key
|
||||
/// presses when in the alternate screen (e.g. when running applications
|
||||
/// like vim or less). The terminal can still set and unset this mode.
|
||||
///
|
||||
/// Default: on
|
||||
pub alternate_scroll: Option<AlternateScroll>,
|
||||
/// Sets whether the option key behaves as the meta key.
|
||||
///
|
||||
/// Default: false
|
||||
pub option_as_meta: Option<bool>,
|
||||
/// Whether or not selecting text in the terminal will automatically
|
||||
/// copy to the system clipboard.
|
||||
///
|
||||
/// Default: false
|
||||
pub copy_on_select: Option<bool>,
|
||||
/// Whether to keep the text selection after copying it to the clipboard.
|
||||
///
|
||||
/// Default: false
|
||||
pub keep_selection_on_copy: Option<bool>,
|
||||
/// Whether to show the terminal button in the status bar.
|
||||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
pub dock: Option<TerminalDockPosition>,
|
||||
/// Default width when the terminal is docked to the left or right.
|
||||
///
|
||||
/// Default: 640
|
||||
pub default_width: Option<f32>,
|
||||
/// Default height when the terminal is docked to the bottom.
|
||||
///
|
||||
/// Default: 320
|
||||
pub default_height: Option<f32>,
|
||||
/// Activates the python virtual environment, if one is found, in the
|
||||
/// terminal's working directory (as resolved by the working_directory
|
||||
/// setting). Set this to "off" to disable this behavior.
|
||||
///
|
||||
/// Default: on
|
||||
pub detect_venv: Option<VenvSettings>,
|
||||
/// The maximum number of lines to keep in the scrollback history.
|
||||
/// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
|
||||
/// 0 disables the scrolling.
|
||||
/// Existing terminals will not pick up this change until they are recreated.
|
||||
/// See <a href="https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213">Alacritty documentation</a> for more information.
|
||||
///
|
||||
/// Default: 10_000
|
||||
pub max_scroll_history_lines: Option<usize>,
|
||||
/// Toolbar related settings
|
||||
pub toolbar: Option<ToolbarContent>,
|
||||
/// Scrollbar-related settings
|
||||
pub scrollbar: Option<ScrollbarSettingsContent>,
|
||||
/// The minimum APCA perceptual contrast between foreground and background colors.
|
||||
///
|
||||
/// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
|
||||
/// especially for dark mode. Values range from 0 to 106.
|
||||
///
|
||||
/// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
|
||||
/// https://readtech.org/ARC/tests/bronze-simple-mode/
|
||||
/// - 0: No contrast adjustment
|
||||
/// - 45: Minimum for large fluent text (36px+)
|
||||
/// - 60: Minimum for other content text
|
||||
/// - 75: Minimum for body text
|
||||
/// - 90: Preferred for body text
|
||||
///
|
||||
/// Default: 45
|
||||
pub minimum_contrast: Option<f32>,
|
||||
}
|
||||
|
||||
impl settings::Settings for TerminalSettings {
|
||||
type FileContent = TerminalSettingsContent;
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
let settings: Self = sources.json_merge()?;
|
||||
|
||||
// Validate minimum_contrast for APCA
|
||||
if settings.minimum_contrast < 0.0 || settings.minimum_contrast > 106.0 {
|
||||
anyhow::bail!(
|
||||
"terminal.minimum_contrast must be between 0 and 106, but got {}. \
|
||||
APCA values: 0 = no adjustment, 75 = recommended for body text, 106 = maximum contrast.",
|
||||
settings.minimum_contrast
|
||||
);
|
||||
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
|
||||
let content = content.terminal.clone().unwrap();
|
||||
TerminalSettings {
|
||||
shell: settings_shell_to_task_shell(content.shell.unwrap()),
|
||||
working_directory: content.working_directory.unwrap(),
|
||||
font_size: content.font_size.map(px),
|
||||
font_family: content.font_family,
|
||||
font_fallbacks: content.font_fallbacks.map(|fallbacks| {
|
||||
FontFallbacks::from_fonts(
|
||||
fallbacks
|
||||
.into_iter()
|
||||
.map(|family| family.0.to_string())
|
||||
.collect(),
|
||||
)
|
||||
}),
|
||||
font_features: content.font_features,
|
||||
font_weight: content.font_weight.map(FontWeight),
|
||||
line_height: content.line_height.unwrap(),
|
||||
env: content.env.unwrap(),
|
||||
cursor_shape: content.cursor_shape.map(Into::into),
|
||||
blinking: content.blinking.unwrap(),
|
||||
alternate_scroll: content.alternate_scroll.unwrap(),
|
||||
option_as_meta: content.option_as_meta.unwrap(),
|
||||
copy_on_select: content.copy_on_select.unwrap(),
|
||||
keep_selection_on_copy: content.keep_selection_on_copy.unwrap(),
|
||||
button: content.button.unwrap(),
|
||||
dock: content.dock.unwrap(),
|
||||
default_width: px(content.default_width.unwrap()),
|
||||
default_height: px(content.default_height.unwrap()),
|
||||
detect_venv: content.detect_venv.unwrap(),
|
||||
max_scroll_history_lines: content.max_scroll_history_lines,
|
||||
toolbar: Toolbar {
|
||||
breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(),
|
||||
},
|
||||
scrollbar: ScrollbarSettings {
|
||||
show: content.scrollbar.unwrap().show.flatten(),
|
||||
},
|
||||
minimum_contrast: content.minimum_contrast.unwrap(),
|
||||
}
|
||||
|
||||
Ok(settings)
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
|
||||
let Some(content) = &content.terminal else {
|
||||
return;
|
||||
};
|
||||
self.shell
|
||||
.merge_from(&content.shell.clone().map(settings_shell_to_task_shell));
|
||||
self.working_directory
|
||||
.merge_from(&content.working_directory);
|
||||
if let Some(font_size) = content.font_size.map(px) {
|
||||
self.font_size = Some(font_size)
|
||||
}
|
||||
if let Some(font_family) = content.font_family.clone() {
|
||||
self.font_family = Some(font_family);
|
||||
}
|
||||
if let Some(fallbacks) = content.font_fallbacks.clone() {
|
||||
self.font_fallbacks = Some(FontFallbacks::from_fonts(
|
||||
fallbacks
|
||||
.into_iter()
|
||||
.map(|family| family.0.to_string())
|
||||
.collect(),
|
||||
))
|
||||
}
|
||||
if let Some(font_features) = content.font_features.clone() {
|
||||
self.font_features = Some(font_features)
|
||||
}
|
||||
if let Some(font_weight) = content.font_weight {
|
||||
self.font_weight = Some(FontWeight(font_weight));
|
||||
}
|
||||
self.line_height.merge_from(&content.line_height);
|
||||
if let Some(env) = &content.env {
|
||||
for (key, value) in env {
|
||||
self.env.insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
if let Some(cursor_shape) = content.cursor_shape {
|
||||
self.cursor_shape = Some(cursor_shape.into())
|
||||
}
|
||||
self.blinking.merge_from(&content.blinking);
|
||||
self.alternate_scroll.merge_from(&content.alternate_scroll);
|
||||
self.option_as_meta.merge_from(&content.option_as_meta);
|
||||
self.copy_on_select.merge_from(&content.copy_on_select);
|
||||
self.keep_selection_on_copy
|
||||
.merge_from(&content.keep_selection_on_copy);
|
||||
self.button.merge_from(&content.button);
|
||||
self.dock.merge_from(&content.dock);
|
||||
self.default_width
|
||||
.merge_from(&content.default_width.map(px));
|
||||
self.default_height
|
||||
.merge_from(&content.default_height.map(px));
|
||||
self.detect_venv.merge_from(&content.detect_venv);
|
||||
if let Some(max_scroll_history_lines) = content.max_scroll_history_lines {
|
||||
self.max_scroll_history_lines = Some(max_scroll_history_lines)
|
||||
}
|
||||
self.toolbar.breadcrumbs.merge_from(
|
||||
&content
|
||||
.toolbar
|
||||
.as_ref()
|
||||
.and_then(|toolbar| toolbar.breadcrumbs),
|
||||
);
|
||||
self.scrollbar.show.merge_from(
|
||||
&content
|
||||
.scrollbar
|
||||
.as_ref()
|
||||
.and_then(|scrollbar| scrollbar.show),
|
||||
);
|
||||
self.minimum_contrast.merge_from(&content.minimum_contrast);
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) {
|
||||
let mut default = TerminalSettingsContent::default();
|
||||
let current = content.terminal.as_mut().unwrap_or(&mut default);
|
||||
let name = |s| format!("terminal.integrated.{s}");
|
||||
|
||||
vscode.f32_setting(&name("fontSize"), &mut current.font_size);
|
||||
@@ -290,9 +203,9 @@ impl settings::Settings for TerminalSettings {
|
||||
&name("cursorStyle"),
|
||||
&mut current.cursor_shape,
|
||||
|s| match s {
|
||||
"block" => Some(CursorShape::Block),
|
||||
"line" => Some(CursorShape::Bar),
|
||||
"underline" => Some(CursorShape::Underline),
|
||||
"block" => Some(CursorShapeContent::Block),
|
||||
"line" => Some(CursorShapeContent::Bar),
|
||||
"underline" => Some(CursorShapeContent::Underline),
|
||||
_ => None,
|
||||
},
|
||||
);
|
||||
@@ -316,7 +229,7 @@ impl settings::Settings for TerminalSettings {
|
||||
// TODO: handle arguments
|
||||
let shell_name = format!("{platform}Exec");
|
||||
if let Some(s) = vscode.read_string(&name(&shell_name)) {
|
||||
current.shell = Some(Shell::Program(s.to_owned()))
|
||||
current.shell = Some(settings::Shell::Program(s.to_owned()))
|
||||
}
|
||||
|
||||
if let Some(env) = vscode
|
||||
@@ -337,81 +250,12 @@ impl settings::Settings for TerminalSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
if content.terminal.is_none() && default != TerminalSettingsContent::default() {
|
||||
content.terminal = Some(default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TerminalLineHeight {
|
||||
/// Use a line height that's comfortable for reading, 1.618
|
||||
#[default]
|
||||
Comfortable,
|
||||
/// Use a standard line height, 1.3. This option is useful for TUIs,
|
||||
/// particularly if they use box characters
|
||||
Standard,
|
||||
/// Use a custom line height.
|
||||
Custom(f32),
|
||||
}
|
||||
|
||||
impl TerminalLineHeight {
|
||||
pub fn value(&self) -> AbsoluteLength {
|
||||
let value = match self {
|
||||
TerminalLineHeight::Comfortable => 1.618,
|
||||
TerminalLineHeight::Standard => 1.3,
|
||||
TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
|
||||
};
|
||||
px(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TerminalBlink {
|
||||
/// Never blink the cursor, ignoring the terminal mode.
|
||||
Off,
|
||||
/// Default the cursor blink to off, but allow the terminal to
|
||||
/// set blinking.
|
||||
TerminalControlled,
|
||||
/// Always blink the cursor, ignoring the terminal mode.
|
||||
On,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlternateScroll {
|
||||
On,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum WorkingDirectory {
|
||||
/// Use the current file's project directory. Will Fallback to the
|
||||
/// first project directory strategy if unsuccessful.
|
||||
CurrentProjectDirectory,
|
||||
/// Use the first project in this workspace's directory.
|
||||
FirstProjectDirectory,
|
||||
/// Always use this platform's home directory (if it can be found).
|
||||
AlwaysHome,
|
||||
/// Always use a specific directory. This value will be shell expanded.
|
||||
/// If this path is not a valid directory the terminal will default to
|
||||
/// this platform's home directory (if it can be found).
|
||||
Always { directory: String },
|
||||
}
|
||||
|
||||
// Toolbar related settings
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct ToolbarContent {
|
||||
/// Whether to display the terminal title in breadcrumbs inside the terminal pane.
|
||||
/// Only shown if the terminal title is not empty.
|
||||
///
|
||||
/// The shell running in the terminal needs to be configured to emit the title.
|
||||
/// Example: `echo -e "\e]2;New Title\007";`
|
||||
///
|
||||
/// Default: true
|
||||
pub breadcrumbs: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CursorShape {
|
||||
@@ -426,6 +270,17 @@ pub enum CursorShape {
|
||||
Hollow,
|
||||
}
|
||||
|
||||
impl From<settings::CursorShapeContent> for CursorShape {
|
||||
fn from(value: settings::CursorShapeContent) -> Self {
|
||||
match value {
|
||||
settings::CursorShapeContent::Block => CursorShape::Block,
|
||||
settings::CursorShapeContent::Underline => CursorShape::Underline,
|
||||
settings::CursorShapeContent::Bar => CursorShape::Bar,
|
||||
settings::CursorShapeContent::Hollow => CursorShape::Hollow,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CursorShape> for AlacCursorShape {
|
||||
fn from(value: CursorShape) -> Self {
|
||||
match value {
|
||||
|
||||
Reference in New Issue
Block a user