Files
zed/crates/settings_ui/src/page_data.rs
T
František GažoandKirill Bulatov bb501d9221 outline_panel: Add setting to hide symbols in multi-buffer views (#63404)
Closes https://github.com/zed-industries/zed/issues/60242

# Objective

When reviewing a branch diff — or project search results — the outline
panel lists every affected excerpt and symbol beneath each file. On a
diff touching more than a handful of files, this consumes so much
vertical space that you cannot see the list of changed files at all,
which is usually the thing you want the panel for.

Existing settings don't cover this:

- `expand_outlines_with_depth: 0` still renders excerpt rows and
top-level symbol rows.
- Collapsing a file row in the panel is coupled to folding that buffer
in the editor, so it can't be used purely to tidy the panel.

## Solution

A display-only mode that reduces multi-buffer views to just the
directory/file tree.

- New `outline_panel.multi_buffer_hide_symbols` setting (default
`false`). When enabled, multi-buffer views render only directory, file
and folded-directory rows — excerpt, symbol and search-match rows are
hidden.
- New `outline panel: toggle symbols` action, plus a button in the panel
footer, which toggles an in-memory per-panel override of the setting
(the same pattern as `editor: toggle inlay hints`). Changing the setting
clears the override, so the setting wins.
- Single-file views are exempt, and the footer button is not shown there
— the panel contains only symbols in that case, so hiding them would
leave it empty.
- The filter is display-only: `collapsed_entries` and editor buffer
folding are never touched.
- Outline fetching is skipped while the mode is active, so large diffs
don't pay for outlines that will not be rendered. Turning the mode off
re-triggers fetching.

This adds one new icon, `file_code_off`, used by the footer button when
symbols are hidden. cc @zed-industries/design for review per the icon
guidelines — it is `file_code` with the slash and outline gaps from
`file_ignored`, keeping the parts of the chevrons that fall on the near
side of the slash.

## Testing

Three new tests in `crates/outline_panel/src/outline_panel.rs`:

- `test_hide_symbols_in_multibuffer` — outline rows are present by
default; only `Fs`/`FoldedDirs` entries remain once the mode is enabled
via the setting (which also exercises the settings observer) and via the
action; outline rows return when it is toggled off, confirming outlines
are re-fetched; asserts `collapsed_entries` is unchanged at every step,
covering the display-only guarantee.
- `test_hide_symbols_exempts_singleton` — a single-file view still
renders its outline with the setting enabled.
- `test_hide_symbols_hides_search_matches` — project search match rows
are hidden while files and directories still render, and return when
toggled off.

`cargo test -p outline_panel` (15 passing), `cargo test -p icons`,
`cargo test -p settings_ui -p settings_content`, and `./script/clippy`
on the touched crates are all green.

Tested manually on macOS against a branch diff, project search results,
and single-file buffers, checking both the setting and the action (from
the panel footer button and from the command palette with focus outside
the panel), and that expanding/collapsing directories and editor folding
still behave as before.

## Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

## Showcase

<!-- Screenshots go here: branch-diff before/after, footer button icon
states, search before/after, single-file exemption -->


|  | Symbols shown (default) | Symbols hidden |
| --- | --- | --- |
| **Footer toggle** | <img
src="https://github.com/user-attachments/assets/393f128d-1a7c-43a8-97b8-8176a07b45c7"
width="260"> | <img
src="https://github.com/user-attachments/assets/87c60dd4-d5b7-48f8-9255-226c4a94a586"
width="260"> |
| **Branch diff** | <img
src="https://github.com/user-attachments/assets/79357325-7d7d-40f7-8067-4ae59a824f87"
width="420"> | <img
src="https://github.com/user-attachments/assets/8a3bbc7e-90d4-4638-9f1d-1ba3091eccfd"
width="420"> |
| **Project search** | <img
src="https://github.com/user-attachments/assets/76e3cf18-d624-42f8-8801-f496bf54a552"
width="420"> | <img
src="https://github.com/user-attachments/assets/2a34e899-b9f4-465e-b571-75d3d54d00eb"
width="420"> |

Single-file views are unaffected — symbols still render, and the toggle
is not offered:

<img
src="https://github.com/user-attachments/assets/76c47884-96db-4071-bcc1-931c4283e36f"
width="640">

---

Release Notes:

- Fixed too many outlines shown per excerpt in the outline panel. Added
an `outline_panel.multi_buffer_hide_symbols` setting and an `outline
panel: toggle symbols` action to show only files and directories in the
outline panel for multi-buffer views.

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
2026-08-31 14:36:04 +00:00

10953 lines
489 KiB
Rust

use gpui::{Action as _, App};
use itertools::Itertools as _;
use settings::{
AudioInputDeviceName, AudioOutputDeviceName, EditPredictionDataCollectionChoice,
LanguageSettingsContent, SemanticTokens, SettingsContent,
};
use std::sync::{Arc, OnceLock};
use strum::{EnumMessage, IntoDiscriminant as _, VariantArray};
use theme::SystemAppearance;
use ui::IntoElement;
use crate::{
ActionLink, DynamicItem, PROJECT, SettingField, SettingItem, SettingsFieldMetadata,
SettingsPage, SettingsPageItem, SubPageLink, USER, active_language, all_language_names,
pages::{
open_audio_test_window, render_edit_prediction_setup_page, render_external_agents_page,
render_llm_providers_page, render_mcp_servers_page, render_sandbox_settings_page,
render_skills_setup_page, render_tool_permissions_setup_page,
},
};
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_AUDIO_OUTPUT: AudioOutputDeviceName = AudioOutputDeviceName(None);
const DEFAULT_EMPTY_AUDIO_OUTPUT: Option<&AudioOutputDeviceName> = Some(&DEFAULT_AUDIO_OUTPUT);
const DEFAULT_AUDIO_INPUT: AudioInputDeviceName = AudioInputDeviceName(None);
const DEFAULT_EMPTY_AUDIO_INPUT: Option<&AudioInputDeviceName> = Some(&DEFAULT_AUDIO_INPUT);
macro_rules! concat_sections {
(@vec, $($arr:expr),+ $(,)?) => {{
let total_len = 0_usize $(+ $arr.len())+;
let mut out = Vec::with_capacity(total_len);
$(
out.extend($arr);
)+
out
}};
($($arr:expr),+ $(,)?) => {{
let total_len = 0_usize $(+ $arr.len())+;
let mut out: Box<[std::mem::MaybeUninit<_>]> = Box::new_uninit_slice(total_len);
let mut index = 0usize;
$(
let array = $arr;
for item in array {
out[index].write(item);
index += 1;
}
)+
debug_assert_eq!(index, total_len);
// SAFETY: we wrote exactly `total_len` elements.
unsafe { out.assume_init() }
}};
}
pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
vec![
general_page(cx),
appearance_page(),
keymap_page(),
editor_page(),
languages_and_tools_page(cx),
search_and_files_page(),
window_and_layout_page(),
panels_page(),
debugger_page(),
terminal_page(),
version_control_page(),
collaboration_page(),
ai_page(cx),
network_page(),
developer_page(cx),
]
}
fn developer_page(cx: &App) -> SettingsPage {
use feature_flags::FeatureFlagAppExt as _;
let mut items: Vec<SettingsPageItem> = Vec::new();
// Feature flag overrides are a staff-only affordance, so only surface the section when the overrides are enabled.
if cx.feature_flag_overrides_enabled() {
items.push(SettingsPageItem::SectionHeader("Feature Flags"));
items.push(SettingsPageItem::SubPageLink(SubPageLink {
title: "Feature Flags".into(),
r#type: Default::default(),
description: None,
search_aliases: &[],
json_path: Some("feature_flags"),
in_json: true,
files: USER,
render: crate::pages::render_feature_flags_page,
}));
}
items.push(SettingsPageItem::SectionHeader("Instrumentation"));
items.push(SettingsPageItem::SettingItem(SettingItem {
title: "Performance Profiler",
description: "Collect timing data for foreground and background executor tasks so they can be inspected via `zed: open performance profiler`. May lead to increased memory usage.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("instrumentation.performance_profiler.enabled"),
pick: |settings_content| {
settings_content
.instrumentation
.as_ref()
.and_then(|i| i.performance_profiler.as_ref())
.and_then(|p| p.enabled.as_ref())
},
write: |settings_content, value, _| {
settings_content
.instrumentation
.get_or_insert_default()
.performance_profiler
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}));
SettingsPage {
title: "Developer",
items: items.into_boxed_slice(),
}
}
fn general_page(cx: &App) -> SettingsPage {
fn general_settings_section(_cx: &App) -> Vec<SettingsPageItem> {
vec![
SettingsPageItem::SectionHeader("General Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Accessible Mode",
description: "Optimize Zed's interface for assistive technology such as screen readers. When enabled, otherwise-collapsed controls stay expanded and keyboard-reachable.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("accessible_mode"),
pick: |settings_content| settings_content.workspace.accessible_mode.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.accessible_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "When Closing With No Tabs",
description: "What to do when using the 'close active item' action with no tabs.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("when_closing_with_no_tabs"),
pick: |settings_content| {
settings_content
.workspace
.when_closing_with_no_tabs
.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.when_closing_with_no_tabs = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "On Last Window Closed",
description: "What to do when the last window is closed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("on_last_window_closed"),
pick: |settings_content| {
settings_content.workspace.on_last_window_closed.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.on_last_window_closed = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use System Path Prompts",
description: "Use native OS dialogs for 'Open' and 'Save As'.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("use_system_path_prompts"),
pick: |settings_content| {
settings_content.workspace.use_system_path_prompts.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.use_system_path_prompts = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use System Prompts",
description: "Use native OS dialogs for confirmations.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("use_system_prompts"),
pick: |settings_content| settings_content.workspace.use_system_prompts.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.use_system_prompts = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Redact Private Values",
description: "Hide the values of variables in private files.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("redact_private_values"),
pick: |settings_content| settings_content.editor.redact_private_values.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.redact_private_values = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Private Files",
description: "Globs to match against file paths to determine if a file is private.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("worktree.private_files"),
pick: |settings_content| {
settings_content.project.worktree.private_files.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.private_files = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "CLI Default Open Behavior",
description: "How `zed <path>` opens directories when no flag is specified.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("cli_default_open_behavior"),
pick: |settings_content| {
settings_content
.workspace
.cli_default_open_behavior
.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.cli_default_open_behavior = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
should_do_titlecase: Some(false),
..Default::default()
})),
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Reveal If Open",
description: "when enabled, zed will prefer already-open buffers.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("reveal_if_open"),
pick: |settings_content| settings_content.workspace.reveal_if_open.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.reveal_if_open = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Default Open Behavior",
description: "How projects open from the UI by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("default_open_behavior"),
pick: |settings_content| {
settings_content.workspace.default_open_behavior.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.default_open_behavior = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
should_do_titlecase: Some(false),
..Default::default()
})),
files: USER,
}),
]
}
fn security_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Security"),
SettingsPageItem::SettingItem(SettingItem {
title: "Trust All Projects By Default",
description: "When opening Zed, avoid Restricted Mode by auto-trusting all projects, enabling use of all features without having to give permission to each new project.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("session.trust_all_projects"),
pick: |settings_content| {
settings_content
.session
.as_ref()
.and_then(|session| session.trust_all_worktrees.as_ref())
},
write: |settings_content, value, _| {
settings_content
.session
.get_or_insert_default()
.trust_all_worktrees = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn workspace_restoration_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Workspace Restoration"),
SettingsPageItem::SettingItem(SettingItem {
title: "Restore Unsaved Buffers",
description: "Whether or not to restore unsaved buffers on restart.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("session.restore_unsaved_buffers"),
pick: |settings_content| {
settings_content
.session
.as_ref()
.and_then(|session| session.restore_unsaved_buffers.as_ref())
},
write: |settings_content, value, _| {
settings_content
.session
.get_or_insert_default()
.restore_unsaved_buffers = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Restore On Startup",
description: "What to restore from the previous session when opening Zed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("restore_on_startup"),
pick: |settings_content| settings_content.workspace.restore_on_startup.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.restore_on_startup = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn scoped_settings_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Scoped Settings"),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Preview Channel",
description: "Which settings should be activated only in Preview build of Zed.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("preview_channel_settings"),
pick: |settings_content| Some(settings_content),
write: |_settings_content, _value, _| {},
}
.unimplemented(),
),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Settings Profiles",
description: "Any number of settings profiles that are temporarily applied on top of your existing user settings.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("settings_profiles"),
pick: |settings_content| Some(settings_content),
write: |_settings_content, _value, _| {},
}
.unimplemented(),
),
metadata: None,
}),
]
}
fn privacy_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Privacy"),
SettingsPageItem::SettingItem(SettingItem {
title: "Telemetry Diagnostics",
description: "Send debug information like crash reports.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("telemetry.diagnostics"),
pick: |settings_content| {
settings_content
.telemetry
.as_ref()
.and_then(|telemetry| telemetry.diagnostics.as_ref())
},
write: |settings_content, value, _| {
settings_content
.telemetry
.get_or_insert_default()
.diagnostics = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Telemetry Metrics",
description: "Send anonymized usage data like what languages you're using Zed with.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("telemetry.metrics"),
pick: |settings_content| {
settings_content
.telemetry
.as_ref()
.and_then(|telemetry| telemetry.metrics.as_ref())
},
write: |settings_content, value, _| {
settings_content.telemetry.get_or_insert_default().metrics = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Anthropic Data Retention",
description: "Allow sending requests to Anthropic models that cannot be offered with Zero Data Retention.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("telemetry.anthropic_retention"),
pick: |settings_content| {
settings_content
.telemetry
.as_ref()
.and_then(|telemetry| telemetry.anthropic_retention.as_ref())
},
write: |settings_content, value, _| {
settings_content
.telemetry
.get_or_insert_default()
.anthropic_retention = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn auto_update_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Auto Update"),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Update",
description: "Whether or not to automatically check for updates.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("auto_update"),
pick: |settings_content| settings_content.auto_update.as_ref(),
write: |settings_content, value, _| {
settings_content.auto_update = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "General",
items: concat_sections!(
@vec,
general_settings_section(cx),
security_section(),
workspace_restoration_section(),
scoped_settings_section(),
privacy_section(),
auto_update_section(),
)
.into(),
}
}
fn appearance_page() -> SettingsPage {
fn theme_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Theme"),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Theme Mode",
description: "Choose a static, fixed theme or dynamically select themes based on appearance and light/dark modes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("theme$"),
pick: |settings_content| {
Some(&dynamic_variants::<settings::ThemeSelection>()[
settings_content
.theme
.theme
.as_ref()?
.discriminant() as usize])
},
write: |settings_content, value, app: &App| {
let Some(value) = value else {
settings_content.theme.theme = None;
return;
};
let settings_value = settings_content.theme.theme.get_or_insert_default();
*settings_value = match value {
settings::ThemeSelectionDiscriminants::Static => {
let name = match settings_value {
settings::ThemeSelection::Static(_) => return,
settings::ThemeSelection::Dynamic { mode, light, dark } => {
match mode {
theme_settings::ThemeAppearanceMode::Light => light.clone(),
theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
theme_settings::ThemeAppearanceMode::System => {
if SystemAppearance::global(app).is_light() {
light.clone()
} else {
dark.clone()
}
}
}
},
};
settings::ThemeSelection::Static(name)
},
settings::ThemeSelectionDiscriminants::Dynamic => {
let static_name = match settings_value {
settings::ThemeSelection::Static(theme_name) => theme_name.clone(),
settings::ThemeSelection::Dynamic {..} => return,
};
settings::ThemeSelection::Dynamic {
mode: settings::ThemeAppearanceMode::System,
light: static_name.clone(),
dark: static_name,
}
},
};
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(settings_content.theme.theme.as_ref()?.discriminant() as usize)
},
fields: dynamic_variants::<settings::ThemeSelection>().into_iter().map(|variant| {
match variant {
settings::ThemeSelectionDiscriminants::Static => vec![
SettingItem {
files: USER,
title: "Theme Name",
description: "The name of your selected theme.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("theme"),
pick: |settings_content| {
match settings_content.theme.theme.as_ref() {
Some(settings::ThemeSelection::Static(name)) => Some(name),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.theme.get_or_insert_default() {
settings::ThemeSelection::Static(theme_name) => *theme_name = value,
_ => return
}
},
}),
metadata: None,
}
],
settings::ThemeSelectionDiscriminants::Dynamic => vec![
SettingItem {
files: USER,
title: "Mode",
description: "Choose whether to use the selected light or dark theme or to follow your OS appearance configuration.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("theme.mode"),
pick: |settings_content| {
match settings_content.theme.theme.as_ref() {
Some(settings::ThemeSelection::Dynamic { mode, ..}) => Some(mode),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.theme.get_or_insert_default() {
settings::ThemeSelection::Dynamic{ mode, ..} => *mode = value,
_ => return
}
},
}),
metadata: None,
},
SettingItem {
files: USER,
title: "Light Theme",
description: "The theme to use when mode is set to light, or when mode is set to system and it is in light mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("theme.light"),
pick: |settings_content| {
match settings_content.theme.theme.as_ref() {
Some(settings::ThemeSelection::Dynamic { light, ..}) => Some(light),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.theme.get_or_insert_default() {
settings::ThemeSelection::Dynamic{ light, ..} => *light = value,
_ => return
}
},
}),
metadata: None,
},
SettingItem {
files: USER,
title: "Dark Theme",
description: "The theme to use when mode is set to dark, or when mode is set to system and it is in dark mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("theme.dark"),
pick: |settings_content| {
match settings_content.theme.theme.as_ref() {
Some(settings::ThemeSelection::Dynamic { dark, ..}) => Some(dark),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.theme.get_or_insert_default() {
settings::ThemeSelection::Dynamic{ dark, ..} => *dark = value,
_ => return
}
},
}),
metadata: None,
}
],
}
}).collect(),
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Icon Theme",
description: "The custom set of icons Zed will associate with files and directories.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("icon_theme$"),
pick: |settings_content| {
Some(&dynamic_variants::<settings::IconThemeSelection>()[
settings_content
.theme
.icon_theme
.as_ref()?
.discriminant() as usize])
},
write: |settings_content, value, app| {
let Some(value) = value else {
settings_content.theme.icon_theme = None;
return;
};
let settings_value = settings_content.theme.icon_theme.get_or_insert_with(|| {
settings::IconThemeSelection::Static(settings::IconThemeName(theme::default_icon_theme().name.clone().into()))
});
*settings_value = match value {
settings::IconThemeSelectionDiscriminants::Static => {
let name = match settings_value {
settings::IconThemeSelection::Static(_) => return,
settings::IconThemeSelection::Dynamic { mode, light, dark } => {
match mode {
theme_settings::ThemeAppearanceMode::Light => light.clone(),
theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
theme_settings::ThemeAppearanceMode::System => {
if SystemAppearance::global(app).is_light() {
light.clone()
} else {
dark.clone()
}
}
}
},
};
settings::IconThemeSelection::Static(name)
},
settings::IconThemeSelectionDiscriminants::Dynamic => {
let static_name = match settings_value {
settings::IconThemeSelection::Static(theme_name) => theme_name.clone(),
settings::IconThemeSelection::Dynamic {..} => return,
};
settings::IconThemeSelection::Dynamic {
mode: settings::ThemeAppearanceMode::System,
light: static_name.clone(),
dark: static_name,
}
},
};
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(settings_content.theme.icon_theme.as_ref()?.discriminant() as usize)
},
fields: dynamic_variants::<settings::IconThemeSelection>().into_iter().map(|variant| {
match variant {
settings::IconThemeSelectionDiscriminants::Static => vec![
SettingItem {
files: USER,
title: "Icon Theme Name",
description: "The name of your selected icon theme.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("icon_theme$string"),
pick: |settings_content| {
match settings_content.theme.icon_theme.as_ref() {
Some(settings::IconThemeSelection::Static(name)) => Some(name),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.icon_theme.as_mut() {
Some(settings::IconThemeSelection::Static(theme_name)) => *theme_name = value,
_ => return
}
},
}),
metadata: None,
}
],
settings::IconThemeSelectionDiscriminants::Dynamic => vec![
SettingItem {
files: USER,
title: "Mode",
description: "Choose whether to use the selected light or dark icon theme or to follow your OS appearance configuration.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("icon_theme"),
pick: |settings_content| {
match settings_content.theme.icon_theme.as_ref() {
Some(settings::IconThemeSelection::Dynamic { mode, ..}) => Some(mode),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.icon_theme.as_mut() {
Some(settings::IconThemeSelection::Dynamic{ mode, ..}) => *mode = value,
_ => return
}
},
}),
metadata: None,
},
SettingItem {
files: USER,
title: "Light Icon Theme",
description: "The icon theme to use when mode is set to light, or when mode is set to system and it is in light mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("icon_theme.light"),
pick: |settings_content| {
match settings_content.theme.icon_theme.as_ref() {
Some(settings::IconThemeSelection::Dynamic { light, ..}) => Some(light),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.icon_theme.as_mut() {
Some(settings::IconThemeSelection::Dynamic{ light, ..}) => *light = value,
_ => return
}
},
}),
metadata: None,
},
SettingItem {
files: USER,
title: "Dark Icon Theme",
description: "The icon theme to use when mode is set to dark, or when mode is set to system and it is in dark mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("icon_theme.dark"),
pick: |settings_content| {
match settings_content.theme.icon_theme.as_ref() {
Some(settings::IconThemeSelection::Dynamic { dark, ..}) => Some(dark),
_ => None
}
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content
.theme
.icon_theme.as_mut() {
Some(settings::IconThemeSelection::Dynamic{ dark, ..}) => *dark = value,
_ => return
}
},
}),
metadata: None,
}
],
}
}).collect(),
}),
]
}
fn buffer_font_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Buffer Font"),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Family",
description: "Font family for editor text.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("buffer_font_family"),
pick: |settings_content| settings_content.theme.buffer_font_family.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.buffer_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Size",
description: "Font size for editor text.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("buffer_font_size"),
pick: |settings_content| settings_content.theme.buffer_font_size.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.buffer_font_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Weight",
description: "Font weight for editor text (100-900).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("buffer_font_weight"),
pick: |settings_content| settings_content.theme.buffer_font_weight.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.buffer_font_weight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Line Height",
description: "Line height for editor text.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("buffer_line_height$"),
pick: |settings_content| {
Some(
&dynamic_variants::<settings::BufferLineHeight>()[settings_content
.theme
.buffer_line_height
.as_ref()?
.discriminant()
as usize],
)
},
write: |settings_content, value, _| {
let Some(value) = value else {
settings_content.theme.buffer_line_height = None;
return;
};
let settings_value = settings_content
.theme
.buffer_line_height
.get_or_insert_with(|| settings::BufferLineHeight::default());
*settings_value = match value {
settings::BufferLineHeightDiscriminants::Comfortable => {
settings::BufferLineHeight::Comfortable
}
settings::BufferLineHeightDiscriminants::Standard => {
settings::BufferLineHeight::Standard
}
settings::BufferLineHeightDiscriminants::Custom => {
let custom_value =
theme_settings::buffer_line_height_from_settings(
*settings_value,
)
.value();
settings::BufferLineHeight::Custom(custom_value)
}
};
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(
settings_content
.theme
.buffer_line_height
.as_ref()?
.discriminant() as usize,
)
},
fields: dynamic_variants::<settings::BufferLineHeight>()
.into_iter()
.map(|variant| match variant {
settings::BufferLineHeightDiscriminants::Comfortable => vec![],
settings::BufferLineHeightDiscriminants::Standard => vec![],
settings::BufferLineHeightDiscriminants::Custom => vec![SettingItem {
files: USER,
title: "Custom Line Height",
description: "Custom line height value (must be at least 1.0).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("buffer_line_height"),
pick: |settings_content| match settings_content
.theme
.buffer_line_height
.as_ref()
{
Some(settings::BufferLineHeight::Custom(value)) => Some(value),
_ => None,
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
match settings_content.theme.buffer_line_height.as_mut() {
Some(settings::BufferLineHeight::Custom(line_height)) => {
*line_height = f32::max(value, 1.0)
}
_ => return,
}
},
}),
metadata: None,
}],
})
.collect(),
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Font Features",
description: "The OpenType features to enable for rendering in text buffers.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("buffer_font_features"),
pick: |settings_content| {
settings_content.theme.buffer_font_features.as_ref()
},
write: |settings_content, value, _| {
settings_content.theme.buffer_font_features = value;
},
}
.unimplemented(),
),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Font Fallbacks",
description: "The font fallbacks to use for rendering in text buffers.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("buffer_font_fallbacks"),
pick: |settings_content| {
settings_content.theme.buffer_font_fallbacks.as_ref()
},
write: |settings_content, value, _| {
settings_content.theme.buffer_font_fallbacks = value;
},
}
.unimplemented(),
),
metadata: None,
}),
]
}
fn ui_font_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("UI Font"),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Family",
description: "Font family for UI elements.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("ui_font_family"),
pick: |settings_content| settings_content.theme.ui_font_family.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.ui_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Size",
description: "Font size for UI elements.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("ui_font_size"),
pick: |settings_content| settings_content.theme.ui_font_size.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.ui_font_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Weight",
description: "Font weight for UI elements (100-900).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("ui_font_weight"),
pick: |settings_content| settings_content.theme.ui_font_weight.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.ui_font_weight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Font Features",
description: "The OpenType features to enable for rendering in UI elements.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("ui_font_features"),
pick: |settings_content| settings_content.theme.ui_font_features.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.ui_font_features = value;
},
}
.unimplemented(),
),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Font Fallbacks",
description: "The font fallbacks to use for rendering in the UI.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("ui_font_fallbacks"),
pick: |settings_content| settings_content.theme.ui_font_fallbacks.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.ui_font_fallbacks = value;
},
}
.unimplemented(),
),
metadata: None,
}),
]
}
fn agent_panel_font_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Agent Panel Font"),
SettingsPageItem::SettingItem(SettingItem {
title: "UI Font Family",
description: "Font family for agent response text in the agent panel. Falls back to the regular UI font family.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent_ui_font_family"),
pick: |settings_content| {
settings_content
.theme
.agent_ui_font_family
.as_ref()
.or(settings_content.theme.ui_font_family.as_ref())
},
write: |settings_content, value, _| {
settings_content.theme.agent_ui_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "UI Font Size",
description: "Font size for agent response text in the agent panel. Falls back to the regular UI font size.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent_ui_font_size"),
pick: |settings_content| {
settings_content
.theme
.agent_ui_font_size
.as_ref()
.or(settings_content.theme.ui_font_size.as_ref())
},
write: |settings_content, value, _| {
settings_content.theme.agent_ui_font_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Buffer Font Family",
description: "Font family for user messages in the agent panel. Falls back to the regular buffer font family.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent_buffer_font_family"),
pick: |settings_content| {
settings_content
.theme
.agent_buffer_font_family
.as_ref()
.or(settings_content.theme.buffer_font_family.as_ref())
},
write: |settings_content, value, _| {
settings_content.theme.agent_buffer_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Buffer Font Size",
description: "Font size for user messages text in the agent panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent_buffer_font_size"),
pick: |settings_content| {
settings_content
.theme
.agent_buffer_font_size
.as_ref()
.or(settings_content.theme.buffer_font_size.as_ref())
},
write: |settings_content, value, _| {
settings_content.theme.agent_buffer_font_size = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn markdown_preview_font_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Markdown Preview Font"),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Family",
description: "Font family for the markdown preview. Falls back to the UI font family.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("markdown_preview_font_family"),
pick: |settings_content| {
settings_content.theme.markdown_preview_font_family.as_ref()
},
write: |settings_content, value, _| {
settings_content.theme.markdown_preview_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Code Font Family",
description: "Font family for code blocks in the markdown preview. Falls back to the editor font family.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("markdown_preview_code_font_family"),
pick: |settings_content| {
settings_content
.theme
.markdown_preview_code_font_family
.as_ref()
},
write: |settings_content, value, _| {
settings_content.theme.markdown_preview_code_font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Size",
description: "Font size for the markdown preview. Falls back to the editor font size.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("markdown_preview_font_size"),
pick: |settings_content| {
settings_content
.theme
.markdown_preview_font_size
.as_ref()
.or(settings_content.theme.buffer_font_size.as_ref())
},
write: |settings_content, value, _| {
settings_content.theme.markdown_preview_font_size = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn text_rendering_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Text Rendering"),
SettingsPageItem::SettingItem(SettingItem {
title: "Text Rendering Mode",
description: "The text rendering mode to use.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("text_rendering_mode"),
pick: |settings_content| {
settings_content.workspace.text_rendering_mode.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.text_rendering_mode = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn cursor_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Cursor"),
SettingsPageItem::SettingItem(SettingItem {
title: "Multi Cursor Modifier",
description: "Modifier key for adding multiple cursors.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("multi_cursor_modifier"),
pick: |settings_content| settings_content.editor.multi_cursor_modifier.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.multi_cursor_modifier = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Blink",
description: "Whether the cursor blinks in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("cursor_blink"),
pick: |settings_content| settings_content.editor.cursor_blink.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.cursor_blink = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape",
description: "Cursor shape for the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("cursor_shape"),
pick: |settings_content| settings_content.editor.cursor_shape.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.cursor_shape = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide Mouse",
description: "When to hide the mouse cursor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("hide_mouse"),
pick: |settings_content| settings_content.hide_mouse.as_ref(),
write: |settings_content, value, _| {
settings_content.hide_mouse = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Reduce Motion",
description: "Whether to reduce non-essential motion, such as loading spinners, by rendering them in a static state.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("reduce_motion"),
pick: |settings_content| settings_content.reduce_motion.as_ref(),
write: |settings_content, value, _| {
settings_content.reduce_motion = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn highlighting_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Highlighting"),
SettingsPageItem::SettingItem(SettingItem {
title: "Unnecessary Code Fade",
description: "How much to fade out unused code (0.0 - 0.9).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("unnecessary_code_fade"),
pick: |settings_content| settings_content.theme.unnecessary_code_fade.as_ref(),
write: |settings_content, value, _| {
settings_content.theme.unnecessary_code_fade = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Current Line Highlight",
description: "How to highlight the current line.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("current_line_highlight"),
pick: |settings_content| {
settings_content.editor.current_line_highlight.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.current_line_highlight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Selection Highlight",
description: "Highlight all occurrences of selected text.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("selection_highlight"),
pick: |settings_content| settings_content.editor.selection_highlight.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.selection_highlight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Rounded Selection",
description: "Whether the text selection should have rounded corners.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("rounded_selection"),
pick: |settings_content| settings_content.editor.rounded_selection.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.rounded_selection = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Minimum Contrast For Highlights",
description: "The minimum APCA perceptual contrast to maintain when rendering text over highlight backgrounds.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimum_contrast_for_highlights"),
pick: |settings_content| {
settings_content
.editor
.minimum_contrast_for_highlights
.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.minimum_contrast_for_highlights = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn guides_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Guides"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Wrap Guides",
description: "Show wrap guides (vertical rulers).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("show_wrap_guides"),
pick: |settings_content| {
settings_content
.project
.all_languages
.defaults
.show_wrap_guides
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project
.all_languages
.defaults
.show_wrap_guides = value;
},
}),
metadata: None,
files: USER | PROJECT,
}),
// todo(settings_ui): This needs a custom component
SettingsPageItem::SettingItem(SettingItem {
title: "Wrap Guides",
description: "Character counts at which to show wrap guides.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("wrap_guides"),
pick: |settings_content| {
settings_content
.project
.all_languages
.defaults
.wrap_guides
.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.all_languages.defaults.wrap_guides = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
let items: Box<[SettingsPageItem]> = concat_sections!(
theme_section(),
buffer_font_section(),
ui_font_section(),
agent_panel_font_section(),
markdown_preview_font_section(),
text_rendering_section(),
cursor_section(),
highlighting_section(),
guides_section(),
);
SettingsPage {
title: "Appearance",
items,
}
}
fn keymap_page() -> SettingsPage {
fn keybindings_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Keybindings"),
SettingsPageItem::ActionLink(ActionLink {
title: "Edit Keybindings".into(),
description: Some("Customize keybindings in the keymap editor.".into()),
button_text: "Open Keymap".into(),
on_click: Arc::new(|settings_window, window, cx| {
let Some(original_window) = settings_window.original_window else {
return;
};
original_window
.update(cx, |_workspace, original_window, cx| {
original_window
.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx);
original_window.activate_window();
})
.ok();
window.remove_window();
}),
files: USER,
}),
]
}
fn base_keymap_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Base Keymap"),
SettingsPageItem::SettingItem(SettingItem {
title: "Base Keymap",
description: "The name of a base set of key bindings to use.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("base_keymap"),
pick: |settings_content| settings_content.base_keymap.as_ref(),
write: |settings_content, value, _| {
settings_content.base_keymap = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
should_do_titlecase: Some(false),
..Default::default()
})),
files: USER,
}),
]
}
fn modal_editing_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Modal Editing"),
SettingsPageItem::SettingItem(SettingItem {
title: "Vim Mode",
description: "Enable Vim mode and key bindings.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim_mode"),
pick: |settings_content| settings_content.vim_mode.as_ref(),
write: write_vim_mode,
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Helix Mode",
description: "Enable Helix mode and key bindings.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("helix_mode"),
pick: |settings_content| settings_content.helix_mode.as_ref(),
write: write_helix_mode,
}),
metadata: None,
files: USER,
}),
]
}
let items: Box<[SettingsPageItem]> = concat_sections!(
keybindings_section(),
base_keymap_section(),
modal_editing_section(),
);
SettingsPage {
title: "Keymap",
items,
}
}
fn editor_page() -> SettingsPage {
fn auto_save_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Auto Save"),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Auto Save Mode",
description: "When to auto save buffer changes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("autosave$"),
pick: |settings_content| {
Some(
&dynamic_variants::<settings::AutosaveSetting>()[settings_content
.workspace
.autosave
.as_ref()?
.discriminant()
as usize],
)
},
write: |settings_content, value, _| {
let Some(value) = value else {
settings_content.workspace.autosave = None;
return;
};
let settings_value = settings_content
.workspace
.autosave
.get_or_insert_with(|| settings::AutosaveSetting::Off);
*settings_value = match value {
settings::AutosaveSettingDiscriminants::Off => {
settings::AutosaveSetting::Off
}
settings::AutosaveSettingDiscriminants::AfterDelay => {
let milliseconds = match settings_value {
settings::AutosaveSetting::AfterDelay { milliseconds } => {
*milliseconds
}
_ => settings::DelayMs(1000),
};
settings::AutosaveSetting::AfterDelay { milliseconds }
}
settings::AutosaveSettingDiscriminants::OnFocusChange => {
settings::AutosaveSetting::OnFocusChange
}
settings::AutosaveSettingDiscriminants::OnWindowChange => {
settings::AutosaveSetting::OnWindowChange
}
};
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(settings_content.workspace.autosave.as_ref()?.discriminant() as usize)
},
fields: dynamic_variants::<settings::AutosaveSetting>()
.into_iter()
.map(|variant| match variant {
settings::AutosaveSettingDiscriminants::Off => vec![],
settings::AutosaveSettingDiscriminants::AfterDelay => vec![SettingItem {
files: USER,
title: "Delay (milliseconds)",
description: "Save after inactivity period (in milliseconds).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("autosave.after_delay.milliseconds"),
pick: |settings_content| match settings_content
.workspace
.autosave
.as_ref()
{
Some(settings::AutosaveSetting::AfterDelay {
milliseconds,
}) => Some(milliseconds),
_ => None,
},
write: |settings_content, value, _| {
let Some(value) = value else {
settings_content.workspace.autosave = None;
return;
};
match settings_content.workspace.autosave.as_mut() {
Some(settings::AutosaveSetting::AfterDelay {
milliseconds,
}) => *milliseconds = value,
_ => return,
}
},
}),
metadata: None,
}],
settings::AutosaveSettingDiscriminants::OnFocusChange => vec![],
settings::AutosaveSettingDiscriminants::OnWindowChange => vec![],
})
.collect(),
}),
]
}
fn which_key_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Which-key Menu"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Which-key Menu",
description: "Display the which-key menu with matching bindings while a multi-stroke binding is pending.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("which_key.enabled"),
pick: |settings_content| {
settings_content
.which_key
.as_ref()
.and_then(|settings| settings.enabled.as_ref())
},
write: |settings_content, value, _| {
settings_content.which_key.get_or_insert_default().enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Menu Delay",
description: "Delay in milliseconds before the which-key menu appears.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("which_key.delay_ms"),
pick: |settings_content| {
settings_content
.which_key
.as_ref()
.and_then(|settings| settings.delay_ms.as_ref())
},
write: |settings_content, value, _| {
settings_content.which_key.get_or_insert_default().delay_ms = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn multibuffer_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Multibuffer"),
SettingsPageItem::SettingItem(SettingItem {
title: "Double Click In Multibuffer",
description: "What to do when multibuffer is double-clicked in some of its excerpts.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("double_click_in_multibuffer"),
pick: |settings_content| {
settings_content.editor.double_click_in_multibuffer.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.double_click_in_multibuffer = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Expand Excerpt Lines",
description: "How many lines to expand the multibuffer excerpts by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("expand_excerpt_lines"),
pick: |settings_content| settings_content.editor.expand_excerpt_lines.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.expand_excerpt_lines = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Excerpt Context Lines",
description: "How many lines of context to provide in multibuffer excerpts by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("excerpt_context_lines"),
pick: |settings_content| settings_content.editor.excerpt_context_lines.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.excerpt_context_lines = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Expand Outlines With Depth",
description: "Default depth to expand outline items in the current file.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.expand_outlines_with_depth"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()
.and_then(|outline_panel| {
outline_panel.expand_outlines_with_depth.as_ref()
})
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.expand_outlines_with_depth = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diff View Style",
description: "How to display diffs in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diff_view_style"),
pick: |settings_content| settings_content.editor.diff_view_style.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.diff_view_style = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Minimum Split Diff Width",
description: "The minimum width (in columns) at which the split diff view is used. When the editor is narrower, the diff view automatically switches to unified mode. Set to 0 to disable.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimum_split_diff_width"),
pick: |settings_content| {
settings_content.editor.minimum_split_diff_width.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.minimum_split_diff_width = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn scrolling_section() -> [SettingsPageItem; 9] {
[
SettingsPageItem::SectionHeader("Scrolling"),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Beyond Last Line",
description: "Whether the editor will scroll beyond the last line.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scroll_beyond_last_line"),
pick: |settings_content| {
settings_content.editor.scroll_beyond_last_line.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.scroll_beyond_last_line = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Vertical Scroll Margin",
description: "The number of lines to keep above/below the cursor when auto-scrolling.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vertical_scroll_margin"),
pick: |settings_content| {
settings_content.editor.vertical_scroll_margin.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.vertical_scroll_margin = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Horizontal Scroll Margin",
description: "The number of characters to keep on either side when scrolling with the mouse.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("horizontal_scroll_margin"),
pick: |settings_content| {
settings_content.editor.horizontal_scroll_margin.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.horizontal_scroll_margin = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Sensitivity",
description: "Scroll sensitivity multiplier for both horizontal and vertical scrolling.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scroll_sensitivity"),
pick: |settings_content| settings_content.editor.scroll_sensitivity.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.scroll_sensitivity = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Mouse Wheel Zoom",
description: "Whether to zoom the editor font size with the mouse wheel while holding the primary modifier key.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("mouse_wheel_zoom"),
pick: |settings_content| settings_content.editor.mouse_wheel_zoom.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.mouse_wheel_zoom = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Fast Scroll Sensitivity",
description: "Fast scroll sensitivity multiplier for both horizontal and vertical scrolling.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("fast_scroll_sensitivity"),
pick: |settings_content| {
settings_content.editor.fast_scroll_sensitivity.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.fast_scroll_sensitivity = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Autoscroll On Clicks",
description: "Whether to scroll when clicking near the edge of the visible text area.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("autoscroll_on_clicks"),
pick: |settings_content| settings_content.editor.autoscroll_on_clicks.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.autoscroll_on_clicks = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sticky Scroll",
description: "Whether to stick scopes to the top of the editor",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("sticky_scroll.enabled"),
pick: |settings_content| {
settings_content
.editor
.sticky_scroll
.as_ref()
.and_then(|sticky_scroll| sticky_scroll.enabled.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.sticky_scroll
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn signature_help_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Signature Help"),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Signature Help",
description: "Automatically show a signature help pop-up.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("auto_signature_help"),
pick: |settings_content| settings_content.editor.auto_signature_help.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.auto_signature_help = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Signature Help After Edits",
description: "Show the signature help pop-up after completions or bracket pairs are inserted.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("show_signature_help_after_edits"),
pick: |settings_content| {
settings_content
.editor
.show_signature_help_after_edits
.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.show_signature_help_after_edits = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Snippet Sort Order",
description: "Determines how snippets are sorted relative to other completion items.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("snippet_sort_order"),
pick: |settings_content| settings_content.editor.snippet_sort_order.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.snippet_sort_order = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn hover_popover_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Hover Popover"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Show the informational hover box when moving the mouse over symbols in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("hover_popover_enabled"),
pick: |settings_content| settings_content.editor.hover_popover_enabled.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.hover_popover_enabled = value;
},
}),
metadata: None,
files: USER,
}),
// todo(settings ui): add units to this number input
SettingsPageItem::SettingItem(SettingItem {
title: "Delay",
description: "Time to wait in milliseconds before showing the informational hover box.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("hover_popover_delay"),
pick: |settings_content| settings_content.editor.hover_popover_delay.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.hover_popover_delay = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sticky",
description: "Whether the hover popover sticks when the mouse moves toward it, allowing interaction with its contents.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("hover_popover_sticky"),
pick: |settings_content| settings_content.editor.hover_popover_sticky.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.hover_popover_sticky = value;
},
}),
metadata: None,
files: USER,
}),
// todo(settings ui): add units to this number input
SettingsPageItem::SettingItem(SettingItem {
title: "Hiding Delay",
description: "Time to wait in milliseconds before hiding the hover popover after the mouse moves away.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("hover_popover_hiding_delay"),
pick: |settings_content| {
settings_content.editor.hover_popover_hiding_delay.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.hover_popover_hiding_delay = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn drag_and_drop_selection_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Drag And Drop Selection"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Enable drag and drop selection.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("drag_and_drop_selection.enabled"),
pick: |settings_content| {
settings_content
.editor
.drag_and_drop_selection
.as_ref()
.and_then(|drag_and_drop| drag_and_drop.enabled.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.drag_and_drop_selection
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Delay",
description: "Delay in milliseconds before drag and drop selection starts.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("drag_and_drop_selection.delay"),
pick: |settings_content| {
settings_content
.editor
.drag_and_drop_selection
.as_ref()
.and_then(|drag_and_drop| drag_and_drop.delay.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.drag_and_drop_selection
.get_or_insert_default()
.delay = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn gutter_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("Gutter"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Line Numbers",
description: "Show line numbers in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.line_numbers"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.line_numbers.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.gutter
.get_or_insert_default()
.line_numbers = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Relative Line Numbers",
description: "Controls line number display in the editor's gutter. \"disabled\" shows absolute line numbers, \"enabled\" shows relative line numbers for each absolute line, and \"wrapped\" shows relative line numbers for every line, absolute or wrapped.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("relative_line_numbers"),
pick: |settings_content| settings_content.editor.relative_line_numbers.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.relative_line_numbers = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Runnables",
description: "Show runnable buttons in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.runnables"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.runnables.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.gutter
.get_or_insert_default()
.runnables = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Breakpoints",
description: "Show breakpoints in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.breakpoints"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.breakpoints.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.gutter
.get_or_insert_default()
.breakpoints = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Bookmarks",
description: "Show bookmarks in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.bookmarks"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.bookmarks.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.gutter
.get_or_insert_default()
.bookmarks = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Folds",
description: "Show code folding controls in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.folds"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.folds.as_ref())
},
write: |settings_content, value, _| {
settings_content.editor.gutter.get_or_insert_default().folds = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Min Line Number Digits",
description: "Minimum number of characters to reserve space for in the gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.min_line_number_digits"),
pick: |settings_content| {
settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.min_line_number_digits.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.gutter
.get_or_insert_default()
.min_line_number_digits = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
title: "Git Gutter Width",
description: "Width of the git diff indicators in the gutter. Default scales with the buffer font size.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.git_gutter_width$"),
pick: |settings_content| {
Some(
&dynamic_variants::<settings::GitGutterWidth>()[settings_content
.editor
.gutter
.as_ref()?
.git_gutter_width
.as_ref()?
.discriminant()
as usize],
)
},
write: |settings_content, value, _| {
let gutter = settings_content.editor.gutter.get_or_insert_default();
gutter.git_gutter_width = value.map(|value| match value {
settings::GitGutterWidthDiscriminants::Default => {
settings::GitGutterWidth::Default
}
settings::GitGutterWidthDiscriminants::Custom => {
let width = match gutter.git_gutter_width {
Some(settings::GitGutterWidth::Custom(width)) => {
settings::PixelSetting(*width)
}
_ => settings::PixelSetting(3.0),
};
settings::GitGutterWidth::Custom(width)
}
});
},
}),
metadata: None,
files: USER,
},
pick_discriminant: |settings_content| {
Some(
settings_content
.editor
.gutter
.as_ref()?
.git_gutter_width
.as_ref()?
.discriminant() as usize,
)
},
fields: dynamic_variants::<settings::GitGutterWidth>()
.into_iter()
.map(|variant| match variant {
settings::GitGutterWidthDiscriminants::Default => vec![],
settings::GitGutterWidthDiscriminants::Custom => vec![SettingItem {
files: USER,
title: "Custom Width",
description: "Width in pixels of the git diff indicators.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("gutter.git_gutter_width"),
pick: |settings_content| match settings_content
.editor
.gutter
.as_ref()
.and_then(|gutter| gutter.git_gutter_width.as_ref())
{
Some(settings::GitGutterWidth::Custom(value)) => Some(value),
_ => None,
},
write: |settings_content, value, _| {
let Some(value) = value else {
return;
};
if let Some(settings::GitGutterWidth::Custom(width)) =
settings_content
.editor
.gutter
.as_mut()
.and_then(|gutter| gutter.git_gutter_width.as_mut())
{
*width = value;
}
},
}),
metadata: None,
}],
})
.collect(),
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Inline Code Actions",
description: "Show code action button at start of buffer line.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("inline_code_actions"),
pick: |settings_content| settings_content.editor.inline_code_actions.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.inline_code_actions = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn scrollbar_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("Scrollbar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show",
description: "When to show the scrollbar in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar"),
pick: |settings_content| {
settings_content.editor.scrollbar.as_ref()?.show.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursors",
description: "Show cursor positions in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.cursors"),
pick: |settings_content| {
settings_content.editor.scrollbar.as_ref()?.cursors.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.cursors = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Diff",
description: "Show Git diff indicators in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.git_diff"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.git_diff
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.git_diff = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Search Results",
description: "Show buffer search result indicators in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.search_results"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.search_results
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.search_results = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Selected Text",
description: "Show selected text occurrences in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.selected_text"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.selected_text
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.selected_text = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Selected Symbol",
description: "Show selected symbol occurrences in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.selected_symbol"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.selected_symbol
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.selected_symbol = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diagnostics",
description: "Which diagnostic indicators to show in the scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.diagnostics"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.diagnostics
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.diagnostics = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Horizontal Scrollbar",
description: "When false, forcefully disables the horizontal scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.axes.horizontal"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.axes
.as_ref()?
.horizontal
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.axes
.get_or_insert_default()
.horizontal = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Vertical Scrollbar",
description: "When false, forcefully disables the vertical scrollbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("scrollbar.axes.vertical"),
pick: |settings_content| {
settings_content
.editor
.scrollbar
.as_ref()?
.axes
.as_ref()?
.vertical
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.scrollbar
.get_or_insert_default()
.axes
.get_or_insert_default()
.vertical = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn minimap_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Minimap"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show",
description: "When to show the minimap in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.show"),
pick: |settings_content| {
settings_content.editor.minimap.as_ref()?.show.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.minimap.get_or_insert_default().show = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Display In",
description: "Where to show the minimap in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.display_in"),
pick: |settings_content| {
settings_content
.editor
.minimap
.as_ref()?
.display_in
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.minimap
.get_or_insert_default()
.display_in = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Thumb",
description: "When to show the minimap thumb.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.thumb"),
pick: |settings_content| {
settings_content.editor.minimap.as_ref()?.thumb.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.minimap
.get_or_insert_default()
.thumb = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Thumb Border",
description: "Border style for the minimap's scrollbar thumb.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.thumb_border"),
pick: |settings_content| {
settings_content
.editor
.minimap
.as_ref()?
.thumb_border
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.minimap
.get_or_insert_default()
.thumb_border = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Current Line Highlight",
description: "How to highlight the current line in the minimap.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.current_line_highlight"),
pick: |settings_content| {
settings_content
.editor
.minimap
.as_ref()
.and_then(|minimap| minimap.current_line_highlight.as_ref())
.or(settings_content.editor.current_line_highlight.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.minimap
.get_or_insert_default()
.current_line_highlight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Max Width Columns",
description: "Maximum number of columns to display in the minimap.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("minimap.max_width_columns"),
pick: |settings_content| {
settings_content
.editor
.minimap
.as_ref()?
.max_width_columns
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.minimap
.get_or_insert_default()
.max_width_columns = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn toolbar_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Toolbar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Breadcrumbs",
description: "Show breadcrumbs.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("toolbar.breadcrumbs"),
pick: |settings_content| {
settings_content
.editor
.toolbar
.as_ref()?
.breadcrumbs
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.toolbar
.get_or_insert_default()
.breadcrumbs = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Quick Actions",
description: "Show quick action buttons (e.g., search, selection, editor controls, etc.).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("toolbar.quick_actions"),
pick: |settings_content| {
settings_content
.editor
.toolbar
.as_ref()?
.quick_actions
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.toolbar
.get_or_insert_default()
.quick_actions = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Selections Menu",
description: "Show the selections menu in the editor toolbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("toolbar.selections_menu"),
pick: |settings_content| {
settings_content
.editor
.toolbar
.as_ref()?
.selections_menu
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.toolbar
.get_or_insert_default()
.selections_menu = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Review",
description: "Show agent review buttons in the editor toolbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("toolbar.agent_review"),
pick: |settings_content| {
settings_content
.editor
.toolbar
.as_ref()?
.agent_review
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.toolbar
.get_or_insert_default()
.agent_review = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Code Actions",
description: "Show code action buttons in the editor toolbar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("toolbar.code_actions"),
pick: |settings_content| {
settings_content
.editor
.toolbar
.as_ref()?
.code_actions
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.toolbar
.get_or_insert_default()
.code_actions = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn vim_settings_section() -> [SettingsPageItem; 14] {
[
SettingsPageItem::SectionHeader("Vim"),
SettingsPageItem::SettingItem(SettingItem {
title: "Default Mode",
description: "The default mode when Vim starts.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.default_mode"),
pick: |settings_content| settings_content.vim.as_ref()?.default_mode.as_ref(),
write: |settings_content, value, _| {
settings_content.vim.get_or_insert_default().default_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Toggle Relative Line Numbers",
description: "Toggle relative line numbers in Vim mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.toggle_relative_line_numbers"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.toggle_relative_line_numbers
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.toggle_relative_line_numbers = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use System Clipboard",
description: "Controls when to use system clipboard in Vim mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.use_system_clipboard"),
pick: |settings_content| {
settings_content.vim.as_ref()?.use_system_clipboard.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.use_system_clipboard = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use Smartcase Find",
description: "Enable smartcase searching in Vim mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.use_smartcase_find"),
pick: |settings_content| {
settings_content.vim.as_ref()?.use_smartcase_find.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.use_smartcase_find = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Global Substitution Default",
description: "When enabled, the :substitute command replaces all matches in a line by default. The 'g' flag then toggles this behavior.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.gdefault"),
pick: |settings_content| settings_content.vim.as_ref()?.gdefault.as_ref(),
write: |settings_content, value, _| {
settings_content.vim.get_or_insert_default().gdefault = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Highlight on Yank Duration",
description: "Duration in milliseconds to highlight yanked text in Vim mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.highlight_on_yank_duration"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.highlight_on_yank_duration
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.highlight_on_yank_duration = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Regex Search",
description: "Use regex search by default in Vim search.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.use_regex_search"),
pick: |settings_content| {
settings_content.vim.as_ref()?.use_regex_search.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.use_regex_search = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Edit Predictions in Normal Mode",
description: "Whether edit predictions are shown in normal mode. By default, edit predictions are only shown in insert and replace modes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.show_edit_predictions_in_normal_mode"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.show_edit_predictions_in_normal_mode
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.show_edit_predictions_in_normal_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape - Normal Mode",
description: "Cursor shape for normal mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.cursor_shape.normal"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.cursor_shape
.as_ref()?
.normal
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.cursor_shape
.get_or_insert_default()
.normal = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape - Insert Mode",
description: "Cursor shape for insert mode. Inherit uses the editor's cursor shape.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.cursor_shape.insert"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.cursor_shape
.as_ref()?
.insert
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.cursor_shape
.get_or_insert_default()
.insert = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape - Replace Mode",
description: "Cursor shape for replace mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.cursor_shape.replace"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.cursor_shape
.as_ref()?
.replace
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.cursor_shape
.get_or_insert_default()
.replace = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape - Visual Mode",
description: "Cursor shape for visual mode.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("vim.cursor_shape.visual"),
pick: |settings_content| {
settings_content
.vim
.as_ref()?
.cursor_shape
.as_ref()?
.visual
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.vim
.get_or_insert_default()
.cursor_shape
.get_or_insert_default()
.visual = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Custom Digraphs",
description: "Custom digraph mappings for Vim mode.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("vim.custom_digraphs"),
pick: |settings_content| {
settings_content.vim.as_ref()?.custom_digraphs.as_ref()
},
write: |settings_content, value, _| {
settings_content.vim.get_or_insert_default().custom_digraphs = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
]
}
let items = concat_sections!(
auto_save_section(),
which_key_section(),
multibuffer_section(),
scrolling_section(),
signature_help_section(),
hover_popover_section(),
drag_and_drop_selection_section(),
gutter_section(),
scrollbar_section(),
minimap_section(),
toolbar_section(),
vim_settings_section(),
language_settings_data(),
);
SettingsPage {
title: "Editor",
items: items,
}
}
fn languages_and_tools_page(cx: &App) -> SettingsPage {
fn file_types_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("File Types"),
SettingsPageItem::SettingItem(SettingItem {
title: "File Type Associations",
description: "A mapping from languages to files and file extensions that should be treated as that language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("file_type_associations"),
pick: |settings_content| {
settings_content.project.all_languages.file_types.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.all_languages.file_types = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn diagnostics_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Diagnostics"),
SettingsPageItem::SettingItem(SettingItem {
title: "Max Severity",
description: "Which level to use to filter out diagnostics displayed in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics_max_severity"),
pick: |settings_content| {
settings_content.editor.diagnostics_max_severity.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.diagnostics_max_severity = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Include Warnings",
description: "Whether to show warnings or not by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.include_warnings"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.include_warnings
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.include_warnings = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn inline_diagnostics_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Inline Diagnostics"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Whether to show diagnostics inline or not.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.inline.enabled"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.inline
.as_ref()?
.enabled
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.inline
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Update Debounce",
description: "The delay in milliseconds to show inline diagnostics after the last diagnostic update.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.inline.update_debounce_ms"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.inline
.as_ref()?
.update_debounce_ms
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.inline
.get_or_insert_default()
.update_debounce_ms = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Padding",
description: "The amount of padding between the end of the source line and the start of the inline diagnostic.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.inline.padding"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.inline
.as_ref()?
.padding
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.inline
.get_or_insert_default()
.padding = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Minimum Column",
description: "The minimum column at which to display inline diagnostics.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.inline.min_column"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.inline
.as_ref()?
.min_column
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.inline
.get_or_insert_default()
.min_column = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn lsp_pull_diagnostics_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("LSP Pull Diagnostics"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Whether to pull for language server-powered diagnostics or not.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.lsp_pull_diagnostics.enabled"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.lsp_pull_diagnostics
.as_ref()?
.enabled
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.lsp_pull_diagnostics
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
// todo(settings_ui): Needs unit
SettingsPageItem::SettingItem(SettingItem {
title: "Debounce",
description: "Minimum time to wait before pulling diagnostics from the language server(s).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.lsp_pull_diagnostics.debounce_ms"),
pick: |settings_content| {
settings_content
.diagnostics
.as_ref()?
.lsp_pull_diagnostics
.as_ref()?
.debounce_ms
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.diagnostics
.get_or_insert_default()
.lsp_pull_diagnostics
.get_or_insert_default()
.debounce_ms = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn lsp_highlights_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("LSP Highlights"),
SettingsPageItem::SettingItem(SettingItem {
title: "Debounce",
description: "The debounce delay before querying highlights from the language.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("lsp_highlight_debounce"),
pick: |settings_content| {
settings_content.editor.lsp_highlight_debounce.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.lsp_highlight_debounce = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn languages_list_section(cx: &App) -> Box<[SettingsPageItem]> {
// todo(settings_ui): Refresh on extension (un)/installed
// Note that `crates/json_schema_store` solves the same problem, there is probably a way to unify the two
std::iter::once(SettingsPageItem::SectionHeader("Languages"))
.chain(all_language_names(cx).into_iter().map(|language_name| {
let link = format!("languages.{language_name}");
SettingsPageItem::SubPageLink(SubPageLink {
title: language_name,
r#type: crate::SubPageType::Language,
description: None,
search_aliases: &[],
json_path: Some(link.leak()),
in_json: true,
files: USER | PROJECT,
render: |this, scroll_handle, window, cx| {
let items: Box<[SettingsPageItem]> = concat_sections!(
language_settings_data(),
non_editor_language_settings_data(),
edit_prediction_language_settings_section()
);
this.render_sub_page_items(
items.iter().enumerate(),
scroll_handle,
window,
cx,
)
.into_any_element()
},
})
}))
.collect()
}
SettingsPage {
title: "Languages & Tools",
items: {
concat_sections!(
non_editor_language_settings_data(),
file_types_section(),
diagnostics_section(),
inline_diagnostics_section(),
lsp_pull_diagnostics_section(),
lsp_highlights_section(),
languages_list_section(cx),
)
},
}
}
fn search_and_files_page() -> SettingsPage {
fn search_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("Search"),
SettingsPageItem::SettingItem(SettingItem {
title: "Whole Word",
description: "Search for whole words by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search.whole_word"),
pick: |settings_content| {
settings_content.editor.search.as_ref()?.whole_word.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.whole_word = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Case Sensitive",
description: "Search case-sensitively by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search.case_sensitive"),
pick: |settings_content| {
settings_content
.editor
.search
.as_ref()?
.case_sensitive
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.case_sensitive = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use Smartcase Search",
description: "Whether to automatically enable case-sensitive search based on the search query.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("use_smartcase_search"),
pick: |settings_content| settings_content.editor.use_smartcase_search.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.use_smartcase_search = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Include Ignored",
description: "Include ignored files in search results by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search.include_ignored"),
pick: |settings_content| {
settings_content
.editor
.search
.as_ref()?
.include_ignored
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.include_ignored = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Regex",
description: "Use regex search by default.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search.regex"),
pick: |settings_content| {
settings_content.editor.search.as_ref()?.regex.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.search.get_or_insert_default().regex = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Search Wrap",
description: "Whether the editor search results will loop.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search_wrap"),
pick: |settings_content| settings_content.editor.search_wrap.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.search_wrap = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Center on Match",
description: "Whether to center the current match in the editor",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("editor.search.center_on_match"),
pick: |settings_content| {
settings_content
.editor
.search
.as_ref()
.and_then(|search| search.center_on_match.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.center_on_match = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Search on Type",
description: "Start searching as you type in project search, without pressing Enter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("editor.search.search_on_type"),
pick: |settings_content| {
settings_content
.editor
.search
.as_ref()
.and_then(|search| search.search_on_type.as_ref())
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.search_on_type = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Seed Search Query From Cursor",
description: "When to populate a new search's query based on the text under the cursor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("seed_search_query_from_cursor"),
pick: |settings_content| {
settings_content
.editor
.seed_search_query_from_cursor
.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.seed_search_query_from_cursor = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn file_finder_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("File Finder"),
// todo: null by default
SettingsPageItem::SettingItem(SettingItem {
title: "Include Ignored in Search",
description: "Use gitignored files when searching.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("file_finder.include_ignored"),
pick: |settings_content| {
settings_content
.file_finder
.as_ref()?
.include_ignored
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.file_finder
.get_or_insert_default()
.include_ignored = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Icons",
description: "Show file icons in the file finder.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("file_finder.file_icons"),
pick: |settings_content| {
settings_content.file_finder.as_ref()?.file_icons.as_ref()
},
write: |settings_content, value, _| {
settings_content
.file_finder
.get_or_insert_default()
.file_icons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Skip Focus For Active In Search",
description: "Whether the file finder should skip focus for the active file in search results.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("file_finder.skip_focus_for_active_in_search"),
pick: |settings_content| {
settings_content
.file_finder
.as_ref()?
.skip_focus_for_active_in_search
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.file_finder
.get_or_insert_default()
.skip_focus_for_active_in_search = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn file_scan_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("File Scan"),
SettingsPageItem::SettingItem(SettingItem {
title: "File Scan Exclusions",
description: "Files or globs of files that will be excluded by Zed entirely. They will be skipped during file scans, file searches, and not be displayed in the project file tree. Takes precedence over \"File Scan Inclusions\"",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("file_scan_exclusions"),
pick: |settings_content| {
settings_content
.project
.worktree
.file_scan_exclusions
.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.file_scan_exclusions = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Scan Inclusions",
description: "Files or globs of files that will be included by Zed, even when ignored by git. This is useful for files that are not tracked by git, but are still important to your project. Note that globs that are overly broad can slow down Zed's file scanning. \"File Scan Exclusions\" takes precedence over these inclusions",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("file_scan_inclusions"),
pick: |settings_content| {
settings_content
.project
.worktree
.file_scan_inclusions
.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.file_scan_inclusions = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Scan Depth",
description: "Maximum directory depth to eagerly index outside of git repositories; contents of directories at this depth or deeper are indexed on demand. Repositories rooted shallower than this depth are always indexed fully. In projects that are not rooted at a git repository, repositories directly inside a root folder activate their git features immediately; deeper ones activate on first use. 0 means no limit and activates all git repositories immediately",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("file_scan_depth"),
pick: |settings_content| {
settings_content.project.worktree.file_scan_depth.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.file_scan_depth = value;
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scan Symbolic Links",
description: "When to scan content of linked directories",
field: Box::new(SettingField {
json_path: Some("scan_symlinks"),
organization_override: None,
pick: |settings_content| {
settings_content.project.worktree.scan_symlinks.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.scan_symlinks = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Restore File State",
description: "Restore previous file state when reopening.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("restore_on_file_reopen"),
pick: |settings_content| {
settings_content.workspace.restore_on_file_reopen.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.restore_on_file_reopen = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Close on File Delete",
description: "Automatically close files that have been deleted.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("close_on_file_delete"),
pick: |settings_content| {
settings_content.workspace.close_on_file_delete.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.close_on_file_delete = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Search & Files",
items: concat_sections![search_section(), file_finder_section(), file_scan_section()],
}
}
fn window_and_layout_page() -> SettingsPage {
fn status_bar_section() -> [SettingsPageItem; 11] {
[
SettingsPageItem::SectionHeader("Status Bar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Project Panel Button",
description: "Show the project panel button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.button"),
pick: |settings_content| {
settings_content.project_panel.as_ref()?.button.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Active Language Button",
description: "Show the active language button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("status_bar.active_language_button"),
pick: |settings_content| {
settings_content
.status_bar
.as_ref()?
.active_language_button
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.status_bar
.get_or_insert_default()
.active_language_button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Active Encoding Button",
description: "Control when to show the active encoding in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("status_bar.active_encoding_button"),
pick: |settings_content| {
settings_content
.status_bar
.as_ref()?
.active_encoding_button
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.status_bar
.get_or_insert_default()
.active_encoding_button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Position Button",
description: "Show the cursor position button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("status_bar.cursor_position_button"),
pick: |settings_content| {
settings_content
.status_bar
.as_ref()?
.cursor_position_button
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.status_bar
.get_or_insert_default()
.cursor_position_button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Line Endings Button",
description: "Show the active line endings button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("status_bar.line_endings_button"),
pick: |settings_content| {
settings_content
.status_bar
.as_ref()?
.line_endings_button
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.status_bar
.get_or_insert_default()
.line_endings_button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Terminal Button",
description: "Show the terminal button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.button"),
pick: |settings_content| settings_content.terminal.as_ref()?.button.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diagnostics Button",
description: "Show the project diagnostics button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("diagnostics.button"),
pick: |settings_content| settings_content.diagnostics.as_ref()?.button.as_ref(),
write: |settings_content, value, _| {
settings_content.diagnostics.get_or_insert_default().button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Project Search Button",
description: "Show the project search button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("search.button"),
pick: |settings_content| {
settings_content.editor.search.as_ref()?.button.as_ref()
},
write: |settings_content, value, _| {
settings_content
.editor
.search
.get_or_insert_default()
.button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Debugger Button",
description: "Show the debugger button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.button"),
pick: |settings_content| settings_content.debugger.as_ref()?.button.as_ref(),
write: |settings_content, value, _| {
settings_content.debugger.get_or_insert_default().button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Active File Name",
description: "Show the name of the active file in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("status_bar.show_active_file"),
pick: |settings_content| {
settings_content
.status_bar
.as_ref()?
.show_active_file
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.status_bar
.get_or_insert_default()
.show_active_file = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn title_bar_section() -> [SettingsPageItem; 11] {
[
SettingsPageItem::SectionHeader("Title Bar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Branch Status Icon",
description: "Show git status indicators on the branch icon in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_branch_status_icon"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_branch_status_icon
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_branch_status_icon = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Branch Name",
description: "Show the branch name button in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_branch_name"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_branch_name
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_branch_name = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Worktree Name",
description: "Show the worktree name button in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_worktree_name"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_worktree_name
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_worktree_name = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Project Items",
description: "Show the project host and name in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_project_items"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_project_items
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_project_items = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Onboarding Banner",
description: "Show banners announcing new features in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_onboarding_banner"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_onboarding_banner
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_onboarding_banner = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Sign In",
description: "Show the sign in button in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_sign_in"),
pick: |settings_content| {
settings_content.title_bar.as_ref()?.show_sign_in.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_sign_in = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show User Menu",
description: "Show the user menu button in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_user_menu"),
pick: |settings_content| {
settings_content.title_bar.as_ref()?.show_user_menu.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_user_menu = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show User Picture",
description: "Show user picture in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_user_picture"),
pick: |settings_content| {
settings_content
.title_bar
.as_ref()?
.show_user_picture
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_user_picture = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Menus",
description: "Show the menus in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.show_menus"),
pick: |settings_content| {
settings_content.title_bar.as_ref()?.show_menus.as_ref()
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.show_menus = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Button Layout",
description:
"(Linux only) choose how window control buttons are laid out in the titlebar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.button_layout$"),
pick: |settings_content| {
Some(
&dynamic_variants::<settings::WindowButtonLayoutContent>()[settings_content
.title_bar
.as_ref()?
.button_layout
.as_ref()?
.discriminant()
as usize],
)
},
write: |settings_content, value, _| {
let Some(value) = value else {
settings_content
.title_bar
.get_or_insert_default()
.button_layout = None;
return;
};
let current_custom_layout = settings_content
.title_bar
.as_ref()
.and_then(|title_bar| title_bar.button_layout.as_ref())
.and_then(|button_layout| match button_layout {
settings::WindowButtonLayoutContent::Custom(layout) => {
Some(layout.clone())
}
_ => None,
});
let button_layout = match value {
settings::WindowButtonLayoutContentDiscriminants::PlatformDefault => {
settings::WindowButtonLayoutContent::PlatformDefault
}
settings::WindowButtonLayoutContentDiscriminants::Standard => {
settings::WindowButtonLayoutContent::Standard
}
settings::WindowButtonLayoutContentDiscriminants::Custom => {
settings::WindowButtonLayoutContent::Custom(
current_custom_layout.unwrap_or_else(|| {
"close:minimize,maximize".to_string()
}),
)
}
};
settings_content
.title_bar
.get_or_insert_default()
.button_layout = Some(button_layout);
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(
settings_content
.title_bar
.as_ref()?
.button_layout
.as_ref()?
.discriminant() as usize,
)
},
fields: dynamic_variants::<settings::WindowButtonLayoutContent>()
.into_iter()
.map(|variant| match variant {
settings::WindowButtonLayoutContentDiscriminants::PlatformDefault => {
vec![]
}
settings::WindowButtonLayoutContentDiscriminants::Standard => vec![],
settings::WindowButtonLayoutContentDiscriminants::Custom => vec![
SettingItem {
files: USER,
title: "Custom Button Layout",
description:
"GNOME-style layout string such as \"close:minimize,maximize\".",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("title_bar.button_layout"),
pick: |settings_content| match settings_content
.title_bar
.as_ref()?
.button_layout
.as_ref()?
{
settings::WindowButtonLayoutContent::Custom(layout) => {
Some(layout)
}
_ => DEFAULT_EMPTY_STRING,
},
write: |settings_content, value, _| {
settings_content
.title_bar
.get_or_insert_default()
.button_layout = value
.map(settings::WindowButtonLayoutContent::Custom);
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
placeholder: Some("close:minimize,maximize"),
..Default::default()
})),
},
],
})
.collect(),
}),
]
}
fn tab_bar_section() -> [SettingsPageItem; 9] {
[
SettingsPageItem::SectionHeader("Tab Bar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Tab Bar",
description: "Show the tab bar in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tab_bar.show"),
pick: |settings_content| settings_content.tab_bar.as_ref()?.show.as_ref(),
write: |settings_content, value, _| {
settings_content.tab_bar.get_or_insert_default().show = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Git Status In Tabs",
description: "Show the Git file status on a tab item.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.git_status"),
pick: |settings_content| settings_content.tabs.as_ref()?.git_status.as_ref(),
write: |settings_content, value, _| {
settings_content.tabs.get_or_insert_default().git_status = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show File Icons In Tabs",
description: "Show the file icon for a tab.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.file_icons"),
pick: |settings_content| settings_content.tabs.as_ref()?.file_icons.as_ref(),
write: |settings_content, value, _| {
settings_content.tabs.get_or_insert_default().file_icons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Tab Close Position",
description: "Position of the close button in a tab.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.close_position"),
pick: |settings_content| {
settings_content.tabs.as_ref()?.close_position.as_ref()
},
write: |settings_content, value, _| {
settings_content.tabs.get_or_insert_default().close_position = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Maximum Tabs",
description: "Maximum open tabs in a pane. Will not close an unsaved tab.",
// todo(settings_ui): The default for this value is null and it's use in code
// is complex, so I'm going to come back to this later
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("max_tabs"),
pick: |settings_content| settings_content.workspace.max_tabs.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.max_tabs = value;
},
}
.unimplemented(),
),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Navigation History Buttons",
description: "Show the navigation history buttons in the tab bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tab_bar.show_nav_history_buttons"),
pick: |settings_content| {
settings_content
.tab_bar
.as_ref()?
.show_nav_history_buttons
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tab_bar
.get_or_insert_default()
.show_nav_history_buttons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Tab Bar Buttons",
description: "Show the tab bar buttons (New, Split Pane, Zoom).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tab_bar.show_tab_bar_buttons"),
pick: |settings_content| {
settings_content
.tab_bar
.as_ref()?
.show_tab_bar_buttons
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tab_bar
.get_or_insert_default()
.show_tab_bar_buttons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Pinned Tabs Layout",
description: "Show pinned tabs in a separate row above unpinned tabs.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tab_bar.show_pinned_tabs_in_separate_row"),
pick: |settings_content| {
settings_content
.tab_bar
.as_ref()?
.show_pinned_tabs_in_separate_row
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tab_bar
.get_or_insert_default()
.show_pinned_tabs_in_separate_row = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn tab_settings_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Tab Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Activate On Close",
description: "What to do after closing the current tab.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.activate_on_close"),
pick: |settings_content| {
settings_content.tabs.as_ref()?.activate_on_close.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tabs
.get_or_insert_default()
.activate_on_close = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Tab Show Diagnostics",
description: "Which files containing diagnostic errors/warnings to mark in the tabs.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.show_diagnostics"),
pick: |settings_content| {
settings_content.tabs.as_ref()?.show_diagnostics.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tabs
.get_or_insert_default()
.show_diagnostics = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Close Button",
description: "Controls the appearance behavior of the tab's close button.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("tabs.show_close_button"),
pick: |settings_content| {
settings_content.tabs.as_ref()?.show_close_button.as_ref()
},
write: |settings_content, value, _| {
settings_content
.tabs
.get_or_insert_default()
.show_close_button = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn preview_tabs_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Preview Tabs"),
SettingsPageItem::SettingItem(SettingItem {
title: "Preview Tabs Enabled",
description: "Show opened editors as preview tabs.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enabled"),
pick: |settings_content| {
settings_content.preview_tabs.as_ref()?.enabled.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Preview From Project Panel",
description: "Whether to open tabs in preview mode when opened from the project panel with a single click.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_preview_from_project_panel"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_preview_from_project_panel
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_preview_from_project_panel = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Preview From File Finder",
description: "Whether to open tabs in preview mode when selected from the file finder.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_preview_from_file_finder"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_preview_from_file_finder
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_preview_from_file_finder = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Preview From Multibuffer",
description: "Whether to open tabs in preview mode when opened from a multibuffer.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_preview_from_multibuffer"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_preview_from_multibuffer
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_preview_from_multibuffer = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Preview Multibuffer From Code Navigation",
description: "Whether to open tabs in preview mode when code navigation is used to open a multibuffer.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_preview_multibuffer_from_code_navigation"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_preview_multibuffer_from_code_navigation
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_preview_multibuffer_from_code_navigation = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Preview File From Code Navigation",
description: "Whether to open tabs in preview mode when code navigation is used to open a single file.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_preview_file_from_code_navigation"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_preview_file_from_code_navigation
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_preview_file_from_code_navigation = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Keep Preview On Code Navigation",
description: "Whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("preview_tabs.enable_keep_preview_on_code_navigation"),
pick: |settings_content| {
settings_content
.preview_tabs
.as_ref()?
.enable_keep_preview_on_code_navigation
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.preview_tabs
.get_or_insert_default()
.enable_keep_preview_on_code_navigation = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn layout_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Layout"),
SettingsPageItem::SettingItem(SettingItem {
title: "Bottom Dock Layout",
description: "Layout mode for the bottom dock.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("bottom_dock_layout"),
pick: |settings_content| settings_content.workspace.bottom_dock_layout.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.bottom_dock_layout = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Centered Layout Left Padding",
description: "Left padding for centered layout.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("centered_layout.left_padding"),
pick: |settings_content| {
settings_content
.workspace
.centered_layout
.as_ref()?
.left_padding
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.workspace
.centered_layout
.get_or_insert_default()
.left_padding = value;
},
}),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Centered Layout Right Padding",
description: "Right padding for centered layout.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("centered_layout.right_padding"),
pick: |settings_content| {
settings_content
.workspace
.centered_layout
.as_ref()?
.right_padding
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.workspace
.centered_layout
.get_or_insert_default()
.right_padding = value;
},
}),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Focus Follows Mouse",
description: "Whether to change focus to a pane when the mouse hovers over it.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("focus_follows_mouse.enabled"),
pick: |settings_content| {
settings_content
.workspace
.focus_follows_mouse
.as_ref()
.and_then(|s| s.enabled.as_ref())
},
write: |settings_content, value, _| {
settings_content
.workspace
.focus_follows_mouse
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Focus Follows Mouse Debounce ms",
description: "Amount of time to wait before changing focus.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("focus_follows_mouse.debounce_ms"),
pick: |settings_content| {
settings_content
.workspace
.focus_follows_mouse
.as_ref()
.and_then(|s| s.debounce_ms.as_ref())
},
write: |settings_content, value, _| {
settings_content
.workspace
.focus_follows_mouse
.get_or_insert_default()
.debounce_ms = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn window_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Window"),
// todo(settings_ui): Should we filter by platform.as_ref()?
SettingsPageItem::SettingItem(SettingItem {
title: "Use System Window Tabs",
description: "(macOS only) whether to allow Windows to tab together.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("use_system_window_tabs"),
pick: |settings_content| {
settings_content.workspace.use_system_window_tabs.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.use_system_window_tabs = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Fullscreen Mode",
description: "(macOS only) which fullscreen mode the toggle fullscreen action enters.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("fullscreen_mode"),
pick: |settings_content| settings_content.workspace.fullscreen_mode.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.fullscreen_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Window Decorations",
description: "(Linux only) whether Zed or your compositor should draw window decorations.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("window_decorations"),
pick: |settings_content| settings_content.workspace.window_decorations.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.window_decorations = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn pane_modifiers_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Pane Modifiers"),
SettingsPageItem::SettingItem(SettingItem {
title: "Inactive Opacity",
description: "Opacity of inactive panels (0.0 - 1.0).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("active_pane_modifiers.inactive_opacity"),
pick: |settings_content| {
settings_content
.workspace
.active_pane_modifiers
.as_ref()?
.inactive_opacity
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.workspace
.active_pane_modifiers
.get_or_insert_default()
.inactive_opacity = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Border Size",
description: "Size of the border surrounding the active pane.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("active_pane_modifiers.border_size"),
pick: |settings_content| {
settings_content
.workspace
.active_pane_modifiers
.as_ref()?
.border_size
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.workspace
.active_pane_modifiers
.get_or_insert_default()
.border_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Zoomed Padding",
description: "Show padding for zoomed panes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("zoomed_padding"),
pick: |settings_content| settings_content.workspace.zoomed_padding.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.zoomed_padding = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn pane_split_direction_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Pane Split Direction"),
SettingsPageItem::SettingItem(SettingItem {
title: "Vertical Split Direction",
description: "Direction to split vertically.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("pane_split_direction_vertical"),
pick: |settings_content| {
settings_content
.workspace
.pane_split_direction_vertical
.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.pane_split_direction_vertical = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Horizontal Split Direction",
description: "Direction to split horizontally.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("pane_split_direction_horizontal"),
pick: |settings_content| {
settings_content
.workspace
.pane_split_direction_horizontal
.as_ref()
},
write: |settings_content, value, _| {
settings_content.workspace.pane_split_direction_horizontal = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Window & Layout",
items: concat_sections![
status_bar_section(),
title_bar_section(),
tab_bar_section(),
tab_settings_section(),
preview_tabs_section(),
layout_section(),
window_section(),
pane_modifiers_section(),
pane_split_direction_section(),
],
}
}
fn panels_page() -> SettingsPage {
fn project_panel_section() -> [SettingsPageItem; 29] {
[
SettingsPageItem::SectionHeader("Project Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Project Panel Dock",
description: "Where to dock the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.dock"),
pick: |settings_content| settings_content.project_panel.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.project_panel.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Project Panel Default Width",
description: "Default width of the project panel in pixels.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.default_width"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.default_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.default_width = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide .gitignore",
description: "Whether to hide the gitignore entries in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.hide_gitignore"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.hide_gitignore
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.hide_gitignore = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Entry Spacing",
description: "Spacing between worktree entries in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.entry_spacing"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.entry_spacing
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.entry_spacing = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Icons",
description: "Show file icons in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.file_icons"),
pick: |settings_content| {
settings_content.project_panel.as_ref()?.file_icons.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.file_icons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Folder Indicator",
description: "What to show for directories in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.folder_indicator"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.folder_indicator
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.folder_indicator = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Status",
description: "Show the Git status in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.git_status"),
pick: |settings_content| {
settings_content.project_panel.as_ref()?.git_status.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.git_status = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Indent Size",
description: "Amount of indentation for nested items.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.indent_size"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.indent_size
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.indent_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Reveal Entries",
description: "Whether to reveal entries in the project panel automatically when a corresponding project entry becomes active.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.auto_reveal_entries"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.auto_reveal_entries
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.auto_reveal_entries = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Starts Open",
description: "Whether the project panel should open on startup.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.starts_open"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.starts_open
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.starts_open = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Fold Directories",
description: "Whether to fold directories automatically and show compact folders when a directory has only one subdirectory inside.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.auto_fold_dirs"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.auto_fold_dirs
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.auto_fold_dirs = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Bold Folder Labels",
description: "Whether to show folder names with bold text in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.bold_folder_labels"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.bold_folder_labels
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.bold_folder_labels = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Scrollbar",
description: "Show the scrollbar in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.scrollbar.show"),
pick: |settings_content| {
show_scrollbar_or_editor(settings_content, |settings_content| {
settings_content
.project_panel
.as_ref()?
.scrollbar
.as_ref()?
.show
.as_ref()
})
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.scrollbar
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Horizontal Scroll",
description: "Whether to allow horizontal scrolling in the project panel. When disabled, the view is always locked to the leftmost position and long file names are clipped.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.scrollbar.horizontal_scroll"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.scrollbar
.as_ref()?
.horizontal_scroll
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.scrollbar
.get_or_insert_default()
.horizontal_scroll = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Diagnostics",
description: "Which files containing diagnostic errors/warnings to mark in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.show_diagnostics"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.show_diagnostics
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.show_diagnostics = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diagnostic Badges",
description: "Show error and warning count badges next to file names in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.diagnostic_badges"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.diagnostic_badges
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.diagnostic_badges = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Status Indicator",
description: "Show a git status indicator next to file names in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.git_status_indicator"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.git_status_indicator
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.git_status_indicator = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sticky Scroll",
description: "Whether to stick parent directories at top of the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.sticky_scroll"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.sticky_scroll
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.sticky_scroll = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Show Indent Guides",
description: "Show indent guides in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.indent_guides.show"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.indent_guides
.as_ref()?
.show
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.indent_guides
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Drag and Drop",
description: "Whether to enable drag-and-drop operations in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.drag_and_drop"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.drag_and_drop
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.drag_and_drop = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide Root",
description: "Whether to hide the root entry when only one folder is open in the window.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.hide_root"),
pick: |settings_content| {
settings_content.project_panel.as_ref()?.hide_root.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.hide_root = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide Hidden",
description: "Whether to hide the hidden entries in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.hide_hidden"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.hide_hidden
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.hide_hidden = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sort Mode",
description: "Sort order for entries in the project panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.sort_mode"),
pick: |settings_content| {
settings_content.project_panel.as_ref()?.sort_mode.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.sort_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sort Order",
description: "Whether to sort file and folder names case-sensitively in the project panel.",
field: Box::new(SettingField {
organization_override: None,
pick: |settings_content| {
settings_content.project_panel.as_ref()?.sort_order.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.sort_order = value;
},
json_path: Some("project_panel.sort_order"),
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Open Files On Create",
description: "Whether to automatically open newly created files in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.auto_open.on_create"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.auto_open
.as_ref()?
.on_create
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.auto_open
.get_or_insert_default()
.on_create = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Open Files On Paste",
description: "Whether to automatically open files after pasting or duplicating them.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.auto_open.on_paste"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.auto_open
.as_ref()?
.on_paste
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.auto_open
.get_or_insert_default()
.on_paste = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Open Files On Drop",
description: "Whether to automatically open files dropped from external sources.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("project_panel.auto_open.on_drop"),
pick: |settings_content| {
settings_content
.project_panel
.as_ref()?
.auto_open
.as_ref()?
.on_drop
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project_panel
.get_or_insert_default()
.auto_open
.get_or_insert_default()
.on_drop = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hidden Files",
description: "Globs to match files that will be considered \"hidden\" and can be hidden from the project panel.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("worktree.hidden_files"),
pick: |settings_content| {
settings_content.project.worktree.hidden_files.as_ref()
},
write: |settings_content, value, _| {
settings_content.project.worktree.hidden_files = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
]
}
fn terminal_panel_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Terminal Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Terminal Dock",
description: "Where to dock the terminal panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.dock"),
pick: |settings_content| settings_content.terminal.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Starts Open",
description: "Whether the terminal panel should open on startup.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.starts_open"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.starts_open.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.starts_open = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Terminal Panel Flexible Sizing",
description: "Whether the terminal panel should use flexible (proportional) sizing when docked to the left or right.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.flexible"),
pick: |settings_content| settings_content.terminal.as_ref()?.flexible.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().flexible = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Count Badge",
description: "Show a badge on the terminal panel icon with the count of open terminals.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.show_count_badge"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.show_count_badge
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.show_count_badge = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn outline_panel_section() -> [SettingsPageItem; 12] {
[
SettingsPageItem::SectionHeader("Outline Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Outline Panel Button",
description: "Show the outline panel button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.button"),
pick: |settings_content| {
settings_content.outline_panel.as_ref()?.button.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Outline Panel Dock",
description: "Where to dock the outline panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.dock"),
pick: |settings_content| settings_content.outline_panel.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.outline_panel.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Outline Panel Default Width",
description: "Default width of the outline panel in pixels.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.default_width"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.default_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.default_width = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Icons",
description: "Show file icons in the outline panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.file_icons"),
pick: |settings_content| {
settings_content.outline_panel.as_ref()?.file_icons.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.file_icons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Folder Indicator",
description: "What to show for directories in the outline panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.folder_indicator"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.folder_indicator
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.folder_indicator = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Status",
description: "Show the Git status in the outline panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.git_status"),
pick: |settings_content| {
settings_content.outline_panel.as_ref()?.git_status.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.git_status = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Indent Size",
description: "Amount of indentation for nested items.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.indent_size"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.indent_size
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.indent_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Reveal Entries",
description: "Whether to reveal when a corresponding outline entry becomes active.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.auto_reveal_entries"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.auto_reveal_entries
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.auto_reveal_entries = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Fold Directories",
description: "Whether to fold directories automatically when a directory contains only one subdirectory.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.auto_fold_dirs"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.auto_fold_dirs
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.auto_fold_dirs = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
files: USER,
title: "Show Indent Guides",
description: "When to show indent guides in the outline panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.indent_guides.show"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.indent_guides
.as_ref()?
.show
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.indent_guides
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hide Symbols in Multi-Buffers",
description: "Whether to hide symbols, excerpts and search matches in the outline panel when a multi-buffer view is active.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("outline_panel.multi_buffer_hide_symbols"),
pick: |settings_content| {
settings_content
.outline_panel
.as_ref()?
.multi_buffer_hide_symbols
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.outline_panel
.get_or_insert_default()
.multi_buffer_hide_symbols = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn git_panel_section() -> [SettingsPageItem; 18] {
[
SettingsPageItem::SectionHeader("Git Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Panel Button",
description: "Show the Git panel button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.button"),
pick: |settings_content| settings_content.git_panel.as_ref()?.button.as_ref(),
write: |settings_content, value, _| {
settings_content.git_panel.get_or_insert_default().button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Panel Dock",
description: "Where to dock the Git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.dock"),
pick: |settings_content| settings_content.git_panel.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.git_panel.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Starts Open",
description: "Whether the git panel should open on startup.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.starts_open"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.starts_open.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.starts_open = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Panel Default Width",
description: "Default width of the Git panel in pixels.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.default_width"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.default_width.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.default_width = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Git Panel Status Style",
description: "How entry statuses are displayed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.status_style"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.status_style.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.status_style = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Fallback Branch Name",
description: "Default branch name will be when init.defaultbranch is not set in Git.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.fallback_branch_name"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.fallback_branch_name
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.fallback_branch_name = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Sort By",
description: "How to sort entries in the git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.sort_by"),
pick: |settings_content| settings_content.git_panel.as_ref()?.sort_by.as_ref(),
write: |settings_content, value, _| {
settings_content.git_panel.get_or_insert_default().sort_by = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Group By",
description: "How to group entries in the git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.group_by"),
pick: |settings_content| settings_content.git_panel.as_ref()?.group_by.as_ref(),
write: |settings_content, value, _| {
settings_content.git_panel.get_or_insert_default().group_by = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Collapse Untracked Diff",
description: "Whether to collapse untracked files in the diff panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.collapse_untracked_diff"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.collapse_untracked_diff
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.collapse_untracked_diff = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Tree View",
description: "Enable to show entries in tree view list, disable to show in flat view list.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.tree_view"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.tree_view.as_ref()
},
write: |settings_content, value, _| {
settings_content.git_panel.get_or_insert_default().tree_view = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "File Icons",
description: "Show file icons next to the Git status icon.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.file_icons"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.file_icons.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.file_icons = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Folder Indicator",
description: "What to show for directories in the git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.folder_indicator"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.folder_indicator
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.folder_indicator = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diff Stats",
description: "Whether to show the addition/deletion change count next to each file in the Git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.diff_stats"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.diff_stats.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.diff_stats = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Primary Click Behavior",
description: "Default action when clicking a changed file in the Git panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.entry_primary_click_action"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.entry_primary_click_action
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.entry_primary_click_action = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Count Badge",
description: "Whether to show a badge on the git panel icon with the count of uncommitted changes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.show_count_badge"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.show_count_badge
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.show_count_badge = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Commit Title Max Length",
description: "Maximum length of the commit message title before a warning is shown. Set to 0 to disable.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.commit_title_max_length"),
pick: |settings_content| {
settings_content
.git_panel
.as_ref()?
.commit_title_max_length
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.commit_title_max_length = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Bar",
description: "How and when the scrollbar should be displayed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git_panel.scrollbar.show"),
pick: |settings_content| {
show_scrollbar_or_editor(settings_content, |settings_content| {
settings_content
.git_panel
.as_ref()?
.scrollbar
.as_ref()?
.show
.as_ref()
})
},
write: |settings_content, value, _| {
settings_content
.git_panel
.get_or_insert_default()
.scrollbar
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn debugger_panel_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Debugger Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Debugger Panel Dock",
description: "The dock position of the debug panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.dock"),
pick: |settings_content| settings_content.debugger.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.debugger.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn collaboration_panel_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Collaboration Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Collaboration Panel Button",
description: "Show the collaboration panel button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("collaboration_panel.button"),
pick: |settings_content| {
settings_content
.collaboration_panel
.as_ref()?
.button
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.collaboration_panel
.get_or_insert_default()
.button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Collaboration Panel Dock",
description: "Where to dock the collaboration panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("collaboration_panel.dock"),
pick: |settings_content| {
settings_content.collaboration_panel.as_ref()?.dock.as_ref()
},
write: |settings_content, value, _| {
settings_content
.collaboration_panel
.get_or_insert_default()
.dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Collaboration Panel Default Width",
description: "Default width of the collaboration panel in pixels.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("collaboration_panel.dock"),
pick: |settings_content| {
settings_content
.collaboration_panel
.as_ref()?
.default_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.collaboration_panel
.get_or_insert_default()
.default_width = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn agent_panel_section() -> [SettingsPageItem; 7] {
[
SettingsPageItem::SectionHeader("Agent Panel"),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Button",
description: "Whether to show the agent panel button in the status bar.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.button"),
pick: |settings_content| settings_content.agent.as_ref()?.button.as_ref(),
write: |settings_content, value, _| {
settings_content.agent.get_or_insert_default().button = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Dock",
description: "Where to dock the agent panel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.dock"),
pick: |settings_content| settings_content.agent.as_ref()?.dock.as_ref(),
write: |settings_content, value, _| {
settings_content.agent.get_or_insert_default().dock = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Flexible Sizing",
description: "Whether the agent panel should use flexible (proportional) sizing when docked to the left or right.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.flexible"),
pick: |settings_content| settings_content.agent.as_ref()?.flexible.as_ref(),
write: |settings_content, value, _| {
settings_content.agent.get_or_insert_default().flexible = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Default Width",
description: "Default width when the agent panel is docked to the left or right.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.default_width"),
pick: |settings_content| {
settings_content.agent.as_ref()?.default_width.as_ref()
},
write: |settings_content, value, _| {
settings_content.agent.get_or_insert_default().default_width = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Agent Panel Default Height",
description: "Default height when the agent panel is docked to the bottom.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.default_height"),
pick: |settings_content| {
settings_content.agent.as_ref()?.default_height.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.default_height = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Limit Content Width",
description: "Whether to constrain the agent panel content to a maximum width, centering it when the panel is wider, for optimal readability.",
field: Box::new(SettingField::<bool> {
organization_override: None,
json_path: Some("agent.limit_content_width"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.limit_content_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.limit_content_width = value;
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
let enabled = settings_content
.agent
.as_ref()?
.limit_content_width
.unwrap_or(true);
Some(if enabled { 1 } else { 0 })
},
fields: vec![
vec![],
vec![SettingItem {
files: USER,
title: "Max Content Width",
description: "Maximum content width in pixels. Content will be centered when the panel is wider than this value.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.max_content_width"),
pick: |settings_content| {
settings_content.agent.as_ref()?.max_content_width.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.max_content_width = value;
},
}),
metadata: None,
}],
],
}),
]
}
SettingsPage {
title: "Panels",
items: concat_sections![
project_panel_section(),
terminal_panel_section(),
outline_panel_section(),
git_panel_section(),
debugger_panel_section(),
collaboration_panel_section(),
agent_panel_section(),
],
}
}
fn debugger_page() -> SettingsPage {
fn general_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("General"),
SettingsPageItem::SettingItem(SettingItem {
title: "Stepping Granularity",
description: "Determines the stepping granularity for debug operations.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.stepping_granularity"),
pick: |settings_content| {
settings_content
.debugger
.as_ref()?
.stepping_granularity
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.debugger
.get_or_insert_default()
.stepping_granularity = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Save Breakpoints",
description: "Whether breakpoints should be reused across Zed sessions.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.save_breakpoints"),
pick: |settings_content| {
settings_content
.debugger
.as_ref()?
.save_breakpoints
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.debugger
.get_or_insert_default()
.save_breakpoints = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Timeout",
description: "Time in milliseconds until timeout error when connecting to a TCP debug adapter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.timeout"),
pick: |settings_content| settings_content.debugger.as_ref()?.timeout.as_ref(),
write: |settings_content, value, _| {
settings_content.debugger.get_or_insert_default().timeout = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Log DAP Communications",
description: "Whether to log messages between active debug adapters and Zed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.log_dap_communications"),
pick: |settings_content| {
settings_content
.debugger
.as_ref()?
.log_dap_communications
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.debugger
.get_or_insert_default()
.log_dap_communications = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Format DAP Log Messages",
description: "Whether to format DAP messages when adding them to debug adapter logger.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("debugger.format_dap_log_messages"),
pick: |settings_content| {
settings_content
.debugger
.as_ref()?
.format_dap_log_messages
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.debugger
.get_or_insert_default()
.format_dap_log_messages = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Debugger",
items: concat_sections![general_section()],
}
}
fn terminal_page() -> SettingsPage {
fn environment_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Environment"),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER | PROJECT,
title: "Shell",
description: "What shell to use when opening a terminal.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.shell$"),
pick: |settings_content| {
Some(&dynamic_variants::<settings::Shell>()[
settings_content
.terminal
.as_ref()?
.project
.shell
.as_ref()?
.discriminant() as usize
])
},
write: |settings_content, value, _| {
let Some(value) = value else {
if let Some(terminal) = settings_content.terminal.as_mut() {
terminal.project.shell = None;
}
return;
};
let settings_value = settings_content
.terminal
.get_or_insert_default()
.project
.shell
.get_or_insert_with(|| settings::Shell::default());
let default_shell = if cfg!(target_os = "windows") {
"powershell.exe"
} else {
"sh"
};
*settings_value = match value {
settings::ShellDiscriminants::System => settings::Shell::System,
settings::ShellDiscriminants::Program => {
let program = match settings_value {
settings::Shell::Program(program) => program.clone(),
settings::Shell::WithArguments { program, .. } => program.clone(),
_ => String::from(default_shell),
};
settings::Shell::Program(program)
}
settings::ShellDiscriminants::WithArguments => {
let (program, args, title_override) = match settings_value {
settings::Shell::Program(program) => (program.clone(), vec![], None),
settings::Shell::WithArguments {
program,
args,
title_override,
} => (program.clone(), args.clone(), title_override.clone()),
_ => (String::from(default_shell), 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 | PROJECT,
title: "Program",
description: "The shell program to use.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.shell"),
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 | PROJECT,
title: "Program",
description: "The shell program to run.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.shell.program"),
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 | PROJECT,
title: "Arguments",
description: "The arguments to pass to the shell program.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.shell.args"),
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 | PROJECT,
title: "Title Override",
description: "An optional string to override the title of the terminal tab.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.shell.title_override"),
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_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,
},
],
})
.collect(),
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER | PROJECT,
title: "Working Directory",
description: "What working directory to use when launching the terminal.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.working_directory$"),
pick: |settings_content| {
Some(&dynamic_variants::<settings::WorkingDirectory>()[
settings_content
.terminal
.as_ref()?
.project
.working_directory
.as_ref()?
.discriminant() as usize
])
},
write: |settings_content, value, _| {
let Some(value) = value else {
if let Some(terminal) = settings_content.terminal.as_mut() {
terminal.project.working_directory = None;
}
return;
};
let settings_value = settings_content
.terminal
.get_or_insert_default()
.project
.working_directory
.get_or_insert_with(|| settings::WorkingDirectory::CurrentProjectDirectory);
*settings_value = match value {
settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => {
settings::WorkingDirectory::CurrentFileDirectory
},
settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => {
settings::WorkingDirectory::CurrentProjectDirectory
}
settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => {
settings::WorkingDirectory::FirstProjectDirectory
}
settings::WorkingDirectoryDiscriminants::AlwaysHome => {
settings::WorkingDirectory::AlwaysHome
}
settings::WorkingDirectoryDiscriminants::Always => {
let directory = match settings_value {
settings::WorkingDirectory::Always { .. } => return,
_ => String::new(),
};
settings::WorkingDirectory::Always { directory }
}
};
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
Some(
settings_content
.terminal
.as_ref()?
.project
.working_directory
.as_ref()?
.discriminant() as usize,
)
},
fields: dynamic_variants::<settings::WorkingDirectory>()
.into_iter()
.map(|variant| match variant {
settings::WorkingDirectoryDiscriminants::CurrentFileDirectory => vec![],
settings::WorkingDirectoryDiscriminants::CurrentProjectDirectory => vec![],
settings::WorkingDirectoryDiscriminants::FirstProjectDirectory => vec![],
settings::WorkingDirectoryDiscriminants::AlwaysHome => vec![],
settings::WorkingDirectoryDiscriminants::Always => vec![SettingItem {
files: USER | PROJECT,
title: "Directory",
description: "The directory path to use (will be shell expanded).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.working_directory.always"),
pick: |settings_content| {
match settings_content.terminal.as_ref()?.project.working_directory.as_ref() {
Some(settings::WorkingDirectory::Always { directory }) => Some(directory),
_ => None,
}
},
write: |settings_content, value, _| {
let value = value.unwrap_or_default();
match settings_content
.terminal
.get_or_insert_default()
.project
.working_directory
.as_mut()
{
Some(settings::WorkingDirectory::Always { directory }) => *directory = value,
_ => return,
}
},
}),
metadata: None,
}],
})
.collect(),
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Environment Variables",
description: "Key-value pairs to add to the terminal's environment.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.env"),
pick: |settings_content| settings_content.terminal.as_ref()?.project.env.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().project.env = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Detect Virtual Environment",
description: "Activates the Python virtual environment, if one is found, in the terminal's working directory.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.detect_venv"),
pick: |settings_content| settings_content.terminal.as_ref()?.project.detect_venv.as_ref(),
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.project
.detect_venv = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn font_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Font"),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Size",
description: "Font size for terminal text. If not set, defaults to buffer font size.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.font_size"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()
.and_then(|terminal| terminal.font_size.as_ref())
.or(settings_content.theme.buffer_font_size.as_ref())
},
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().font_size = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Family",
description: "Font family for terminal text. If not set, defaults to buffer font family.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.font_family"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()
.and_then(|terminal| terminal.font_family.as_ref())
.or(settings_content.theme.buffer_font_family.as_ref())
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.font_family = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Fallbacks",
description: "Font fallbacks for terminal text. If not set, defaults to buffer font fallbacks.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.font_fallbacks"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()
.and_then(|terminal| terminal.font_fallbacks.as_ref())
.or(settings_content.theme.buffer_font_fallbacks.as_ref())
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.font_fallbacks = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Weight",
description: "Font weight for terminal text in CSS weight units (100-900).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.font_weight"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.font_weight.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.font_weight = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Font Features",
description: "Font features for terminal text.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.font_features"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()
.and_then(|terminal| terminal.font_features.as_ref())
.or(settings_content.theme.buffer_font_features.as_ref())
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.font_features = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
]
}
fn display_settings_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Display Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Line Height",
description: "Line height for terminal text.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("terminal.line_height"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.line_height.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.line_height = value;
},
}
.unimplemented(),
),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Shape",
description: "Default cursor shape for the terminal (bar, block, underline, or hollow).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.cursor_shape"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.cursor_shape.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.cursor_shape = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cursor Blinking",
description: "Sets the cursor blinking behavior in the terminal.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.blinking"),
pick: |settings_content| settings_content.terminal.as_ref()?.blinking.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().blinking = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Alternate Scroll",
description: "Whether alternate scroll mode is active by default (converts mouse scroll to arrow keys in apps like Vim).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.alternate_scroll"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.alternate_scroll
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.alternate_scroll = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Minimum Contrast",
description: "The minimum APCA perceptual contrast between foreground and background colors (0-106).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.minimum_contrast"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.minimum_contrast
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.minimum_contrast = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn behavior_settings_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Behavior Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Option As Meta",
description: "Whether the option key behaves as the meta key.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.option_as_meta"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.option_as_meta.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.option_as_meta = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Copy On Select",
description: "Whether selecting text in the terminal automatically copies to the system clipboard.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.copy_on_select"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.copy_on_select.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.copy_on_select = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Keep Selection On Copy",
description: "Whether to keep the text selection after copying it to the clipboard.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.keep_selection_on_copy"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.keep_selection_on_copy
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.keep_selection_on_copy = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Open Links In Mouse Mode",
description: "Whether cmd-click (ctrl-click on Linux and Windows) opens hyperlinks even when the terminal application has enabled mouse reporting. When disabled, these clicks are forwarded to the application; links can still be opened with shift-cmd-click.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.open_links_in_mouse_mode"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.open_links_in_mouse_mode
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.open_links_in_mouse_mode = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Audible Bell",
description: "Whether to play a sound when the BEL character (`\\a`, `0x07`) is printed",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.bell"),
pick: |settings_content| settings_content.terminal.as_ref()?.bell.as_ref(),
write: |settings_content, value, _| {
settings_content.terminal.get_or_insert_default().bell = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn layout_settings_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Layout Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Default Width",
description: "Default width when the terminal is docked to the left or right (in pixels).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.default_width"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.default_width.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.default_width = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Default Height",
description: "Default height when the terminal is docked to the bottom (in pixels).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.default_height"),
pick: |settings_content| {
settings_content.terminal.as_ref()?.default_height.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.default_height = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn advanced_settings_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Advanced Settings"),
SettingsPageItem::SettingItem(SettingItem {
title: "Max Scroll History Lines",
description: "Maximum number of lines to keep in scrollback history (max: 100,000; 0 disables scrolling).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.max_scroll_history_lines"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.max_scroll_history_lines
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.max_scroll_history_lines = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Multiplier",
description: "The multiplier for scrolling in the terminal with the mouse wheel",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.scroll_multiplier"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.scroll_multiplier
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.scroll_multiplier = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn toolbar_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Toolbar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Breadcrumbs",
description: "Display the terminal title in breadcrumbs inside the terminal pane.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.toolbar.breadcrumbs"),
pick: |settings_content| {
settings_content
.terminal
.as_ref()?
.toolbar
.as_ref()?
.breadcrumbs
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.toolbar
.get_or_insert_default()
.breadcrumbs = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn scrollbar_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Scrollbar"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Scrollbar",
description: "When to show the scrollbar in the terminal.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("terminal.scrollbar.show"),
pick: |settings_content| {
show_scrollbar_or_editor(settings_content, |settings_content| {
settings_content
.terminal
.as_ref()?
.scrollbar
.as_ref()?
.show
.as_ref()
})
},
write: |settings_content, value, _| {
settings_content
.terminal
.get_or_insert_default()
.scrollbar
.get_or_insert_default()
.show = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Terminal",
items: concat_sections![
environment_section(),
font_section(),
display_settings_section(),
behavior_settings_section(),
layout_settings_section(),
advanced_settings_section(),
toolbar_section(),
scrollbar_section(),
],
}
}
fn version_control_page() -> SettingsPage {
fn git_integration_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Git Integration"),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Disable Git Integration",
description: "Disable all Git integration features in Zed.",
field: Box::new(SettingField::<bool> {
organization_override: None,
json_path: Some("git.disable_git"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.enabled
.as_ref()?
.disable_git
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.enabled
.get_or_insert_default()
.disable_git = value;
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
let disabled = settings_content
.git
.as_ref()?
.enabled
.as_ref()?
.disable_git
.unwrap_or(false);
Some(if disabled { 0 } else { 1 })
},
fields: vec![
vec![],
vec![
SettingItem {
files: USER,
title: "Enable Git Status",
description: "Show Git status information in the editor.",
field: Box::new(SettingField::<bool> {
organization_override: None,
json_path: Some("git.enable_status"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.enabled
.as_ref()?
.enable_status
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.enabled
.get_or_insert_default()
.enable_status = value;
},
}),
metadata: None,
},
SettingItem {
files: USER,
title: "Enable Git Diff",
description: "Show Git diff information in the editor.",
field: Box::new(SettingField::<bool> {
organization_override: None,
json_path: Some("git.enable_diff"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.enabled
.as_ref()?
.enable_diff
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.enabled
.get_or_insert_default()
.enable_diff = value;
},
}),
metadata: None,
},
],
],
}),
]
}
fn git_gutter_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Git Gutter"),
SettingsPageItem::SettingItem(SettingItem {
title: "Visibility",
description: "Control whether Git status is shown in the editor's gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.git_gutter"),
pick: |settings_content| settings_content.git.as_ref()?.git_gutter.as_ref(),
write: |settings_content, value, _| {
settings_content.git.get_or_insert_default().git_gutter = value;
},
}),
metadata: None,
files: USER,
}),
// todo(settings_ui): Figure out the right default for this value in default.json
SettingsPageItem::SettingItem(SettingItem {
title: "Debounce",
description: "Debounce threshold in milliseconds after which changes are reflected in the Git gutter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.gutter_debounce"),
pick: |settings_content| {
settings_content.git.as_ref()?.gutter_debounce.as_ref()
},
write: |settings_content, value, _| {
settings_content.git.get_or_insert_default().gutter_debounce = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn inline_git_blame_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Inline Git Blame"),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
title: "Enabled",
description: "Whether or not to show Git blame data for the currently focused line.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.enabled"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.enabled
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
},
pick_discriminant: |settings_content| {
Some(
*settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.enabled
.as_ref()? as usize,
)
},
fields: vec![
vec![],
vec![SettingItem {
title: "Location",
description: "Where to render Git blame when it is enabled.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.location"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.location
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.location = value;
},
}),
metadata: None,
files: USER,
}],
],
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Delay",
description: "The delay after which the inline blame information is shown.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.delay_ms"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.delay_ms
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.delay_ms = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Padding",
description: "Padding between the end of the source line and the start of the inline blame in columns.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.padding"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.padding
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.padding = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Minimum Column",
description: "The minimum column number at which to show the inline blame information.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.min_column"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.min_column
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.min_column = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Commit Summary",
description: "Show commit summary as part of the inline blame.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.inline_blame.show_commit_summary"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.inline_blame
.as_ref()?
.show_commit_summary
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.inline_blame
.get_or_insert_default()
.show_commit_summary = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn git_blame_view_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Git Blame View"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Avatar",
description: "Show the avatar of the author of the commit.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.blame.show_avatar"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.blame
.as_ref()?
.show_avatar
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.blame
.get_or_insert_default()
.show_avatar = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn branch_picker_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Branch Picker"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Author Name",
description: "Show author name as part of the commit information in branch picker.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.branch_picker.show_author_name"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.branch_picker
.as_ref()?
.show_author_name
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.branch_picker
.get_or_insert_default()
.show_author_name = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn git_hunks_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Git Hunks"),
SettingsPageItem::SettingItem(SettingItem {
title: "Hunk Style",
description: "How Git hunks are displayed visually in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.hunk_style"),
pick: |settings_content| settings_content.git.as_ref()?.hunk_style.as_ref(),
write: |settings_content, value, _| {
settings_content.git.get_or_insert_default().hunk_style = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Diff Base",
description: "Whether git features show changes relative to HEAD (uncommitted changes) or to the default branch (all changes on the current branch).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.diff_base"),
pick: |settings_content| settings_content.git.as_ref()?.diff_base.as_ref(),
write: |settings_content, value, _| {
settings_content.git.get_or_insert_default().diff_base = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Path Style",
description: "Should the name or path be displayed first in the git view.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.path_style"),
pick: |settings_content| settings_content.git.as_ref()?.path_style.as_ref(),
write: |settings_content, value, _| {
settings_content.git.get_or_insert_default().path_style = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Stage/Restore Buttons",
description: "Whether to show the stage and restore buttons on diff hunks.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.show_stage_restore_buttons"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.show_stage_restore_buttons
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.show_stage_restore_buttons = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn file_diff_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("File Diff"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Full File by Default",
description: "Whether newly opened file diffs show the full file instead of changes only.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("git.file_diff.show_full_file"),
pick: |settings_content| {
settings_content
.git
.as_ref()?
.file_diff
.as_ref()?
.show_full_file
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.git
.get_or_insert_default()
.file_diff
.get_or_insert_default()
.show_full_file = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Version Control",
items: concat_sections![
git_integration_section(),
git_gutter_section(),
inline_git_blame_section(),
git_blame_view_section(),
branch_picker_section(),
file_diff_section(),
git_hunks_section(),
],
}
}
fn collaboration_page() -> SettingsPage {
fn calls_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Calls"),
SettingsPageItem::SettingItem(SettingItem {
title: "Mute On Join",
description: "Whether the microphone should be muted when joining a channel or a call.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("calls.mute_on_join"),
pick: |settings_content| settings_content.calls.as_ref()?.mute_on_join.as_ref(),
write: |settings_content, value, _| {
settings_content.calls.get_or_insert_default().mute_on_join = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Share On Join",
description: "Whether your current project should be shared when joining an empty channel.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("calls.share_on_join"),
pick: |settings_content| {
settings_content.calls.as_ref()?.share_on_join.as_ref()
},
write: |settings_content, value, _| {
settings_content.calls.get_or_insert_default().share_on_join = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn audio_settings() -> [SettingsPageItem; 3] {
[
SettingsPageItem::ActionLink(ActionLink {
title: "Test Audio".into(),
description: Some("Test your microphone and speaker setup".into()),
button_text: "Test Audio".into(),
on_click: Arc::new(|_settings_window, window, cx| {
open_audio_test_window(window, cx);
}),
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Output Audio Device",
description: "Select output audio device",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("audio.experimental.output_audio_device"),
pick: |settings_content| {
settings_content
.audio
.as_ref()?
.output_audio_device
.as_ref()
.or(DEFAULT_EMPTY_AUDIO_OUTPUT)
},
write: |settings_content, value, _| {
settings_content
.audio
.get_or_insert_default()
.output_audio_device = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Input Audio Device",
description: "Select input audio device",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("audio.experimental.input_audio_device"),
pick: |settings_content| {
settings_content
.audio
.as_ref()?
.input_audio_device
.as_ref()
.or(DEFAULT_EMPTY_AUDIO_INPUT)
},
write: |settings_content, value, _| {
settings_content
.audio
.get_or_insert_default()
.input_audio_device = value;
},
}),
metadata: None,
files: USER,
}),
]
}
SettingsPage {
title: "Collaboration",
items: concat_sections![calls_section(), audio_settings()],
}
}
fn ai_page(cx: &App) -> SettingsPage {
fn general_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("General"),
SettingsPageItem::SettingItem(SettingItem {
title: "Disable AI",
description: "Whether to disable all AI features in Zed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("disable_ai"),
pick: |settings_content| settings_content.project.disable_ai.as_ref(),
write: |settings_content, value, _| {
settings_content.project.disable_ai = value;
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Threads Sidebar Side",
description: "Which side of the window the threads sidebar appears on.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.sidebar_side"),
pick: |settings_content| settings_content.agent.as_ref()?.sidebar_side.as_ref(),
write: |settings_content, value, _| {
settings_content.agent.get_or_insert_default().sidebar_side = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SubPageLink(SubPageLink {
title: "LLM Providers".into(),
r#type: Default::default(),
json_path: Some("llm_providers"),
description: Some("Configure natively-included model providers.".into()),
search_aliases: &[
"ai",
"amazon",
"anthropic",
"api key",
"azure",
"bedrock",
"chat",
"claude",
"copilot",
"gemini",
"github",
"google",
"gpt",
"grok",
"llama",
"llm",
"lm studio",
"mistral",
"ollama",
"openai",
"opencode",
"provider",
"vercel",
"xai",
],
in_json: false,
files: USER,
render: render_llm_providers_page,
}),
SettingsPageItem::SubPageLink(SubPageLink {
title: "External Agents".into(),
r#type: Default::default(),
json_path: Some("agent_servers"),
description: Some(
"View, add, and remove agents connected through the Agent Client Protocol."
.into(),
),
search_aliases: &[
"acp",
"agent client protocol",
"amp",
"claude agent",
"claude code",
"codex",
"copilot cli",
"cursor",
"external agent",
"factory droid",
"github copilot",
"grok build",
"junie",
"opencode",
],
in_json: false,
files: USER,
render: render_external_agents_page,
}),
SettingsPageItem::SubPageLink(SubPageLink {
title: "MCP Servers".into(),
r#type: Default::default(),
json_path: Some("context_servers"),
description: Some(
"View, add, configure, and remove Model Context Protocol servers.".into(),
),
search_aliases: &["context server", "mcp", "model context protocol"],
in_json: false,
files: USER,
render: render_mcp_servers_page,
}),
]
}
fn agent_configuration_section(_cx: &App) -> Box<[SettingsPageItem]> {
let mut items = vec![SettingsPageItem::SectionHeader("Agent Configuration")];
items.extend([
SettingsPageItem::SubPageLink(SubPageLink {
title: "Skills".into(),
r#type: Default::default(),
json_path: Some(zed_actions::AGENT_SKILLS_SETTINGS_PATH),
description: Some("View and manage agent skills installed globally or in project worktrees.".into()),
search_aliases: &["agent skill", "agent skills", "custom instructions", "skill", "skills"],
in_json: false,
files: USER | PROJECT,
render: render_skills_setup_page,
}),
SettingsPageItem::SubPageLink(SubPageLink {
title: "Sandbox".into(),
r#type: Default::default(),
json_path: Some(zed_actions::AGENT_SANDBOX_SETTINGS_PATH),
description: Some(
"Review and change the elevated terminal sandbox permissions that are always allowed without prompting."
.into(),
),
search_aliases: &[
"allow",
"domain",
"filesystem",
"network",
"sandbox",
"unsandboxed",
"permissions",
],
in_json: true,
files: USER,
render: render_sandbox_settings_page,
}),
SettingsPageItem::SubPageLink(SubPageLink {
title: "Tool Permissions".into(),
r#type: Default::default(),
json_path: Some("agent.tool_permissions"),
description: Some("Set up regex patterns to auto-allow, auto-deny, or always request confirmation, for specific tool inputs.".into()),
search_aliases: &[],
in_json: true,
files: USER,
render: render_tool_permissions_setup_page,
}),
]);
items.extend([
SettingsPageItem::SettingItem(SettingItem {
title: "Single File Review",
description: "When enabled, agent edits will also be displayed in single-file buffers for review.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.single_file_review"),
pick: |settings_content| {
settings_content.agent.as_ref()?.single_file_review.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.single_file_review = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Feedback",
description: "Show voting thumbs up/down icon buttons for feedback on agent edits.",
field: Box::new(SettingField {
organization_override: Some(|org_config| if org_config.is_agent_thread_feedback_enabled {
None
} else {
Some(&false)
}),
json_path: Some("agent.enable_feedback"),
pick: |settings_content| {
settings_content.agent.as_ref()?.enable_feedback.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.enable_feedback = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Notify When Agent Waiting",
description: "Where to show notifications when the agent has completed its response or needs confirmation before running a tool action.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.notify_when_agent_waiting"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.notify_when_agent_waiting
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.notify_when_agent_waiting = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Play Sound When Agent Done",
description: "When to play a sound when the agent has either completed its response, or needs user input.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.play_sound_when_agent_done"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.play_sound_when_agent_done
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.play_sound_when_agent_done = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Expand Edit Card",
description: "Whether to have edit cards in the agent panel expanded, showing a Preview of the diff.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.expand_edit_card"),
pick: |settings_content| {
settings_content.agent.as_ref()?.expand_edit_card.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.expand_edit_card = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Expand Terminal Card",
description: "Whether to have terminal cards in the agent panel expanded, showing the whole command output.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.expand_terminal_card"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.expand_terminal_card
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.expand_terminal_card = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Terminal Thread Init Command",
description: "Command to automatically run when Zed creates a Terminal Thread shell in the agent panel. Runs in your configured shell.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.terminal_init_command"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.terminal_init_command
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.terminal_init_command = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
placeholder: Some("e.g. claude"),
display_confirm_button: true,
display_clear_button: true,
confirm_on_focus_out: true,
treat_missing_text_as_empty: true,
..Default::default()
})),
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Thinking Display",
description: "How thinking blocks should be displayed by default. 'Auto' fully expands during streaming, then auto-collapses when done. 'Preview' auto-expands with a height constraint during streaming. 'Always Expanded' shows full content. 'Always Collapsed' keeps them collapsed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.thinking_display"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.thinking_display
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.thinking_display = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Cancel Generation On Terminal Stop",
description: "Whether clicking the stop button on a running terminal tool should also cancel the agent's generation. Note that this only applies to the stop button, not to ctrl+c inside the terminal.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.cancel_generation_on_terminal_stop"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.cancel_generation_on_terminal_stop
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.cancel_generation_on_terminal_stop = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use Modifier To Send",
description: "Whether to always use cmd-enter (or ctrl-enter on Linux or Windows) to send messages.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.use_modifier_to_send"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.use_modifier_to_send
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.use_modifier_to_send = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Message Editor Min Lines",
description: "Minimum number of lines to display in the agent message editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.message_editor_min_lines"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.message_editor_min_lines
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.message_editor_min_lines = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Turn Stats",
description: "Whether to show turn statistics like elapsed time during generation and final turn duration.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.show_turn_stats"),
pick: |settings_content| {
settings_content.agent.as_ref()?.show_turn_stats.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.show_turn_stats = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Merge Conflict Indicator",
description: "Whether to show the merge conflict indicator in the status bar that offers to resolve conflicts using the agent.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.show_merge_conflict_indicator"),
pick: |settings_content| {
settings_content.agent.as_ref()?.show_merge_conflict_indicator.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.show_merge_conflict_indicator = value;
},
}),
metadata: None,
files: USER,
}),
]);
items.extend([
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Compact",
description: "Automatically compact the agent's context when it grows too large, summarizing earlier messages to free up room in the model's context window.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.auto_compact.enabled"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.auto_compact
.as_ref()?
.enabled
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.auto_compact
.get_or_insert_default()
.enabled = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Compact Threshold",
description: "When auto compaction runs. A percentage string like \"90%\" is measured against the context window. A positive integer is the number of used tokens to compact after. A negative integer is the number of tokens remaining in the context window before compacting.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("agent.auto_compact.threshold"),
pick: |settings_content| {
settings_content
.agent
.as_ref()?
.auto_compact
.as_ref()?
.threshold
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.agent
.get_or_insert_default()
.auto_compact
.get_or_insert_default()
.threshold = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
placeholder: Some("90%"),
..Default::default()
})),
files: USER,
}),
]);
items.into_boxed_slice()
}
fn edit_prediction_display_sub_section() -> [SettingsPageItem; 1] {
[SettingsPageItem::SettingItem(SettingItem {
title: "Display Mode",
description: "When to show edit predictions previews in buffer. The eager mode displays them inline, while the subtle mode displays them only when holding a modifier key.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("edit_prediction.display_mode"),
pick: |settings_content| {
settings_content
.project
.all_languages
.edit_predictions
.as_ref()?
.mode
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project
.all_languages
.edit_predictions
.get_or_insert_default()
.mode = value;
},
}),
metadata: None,
files: USER,
})]
}
SettingsPage {
title: "AI",
items: concat_sections!(
@vec,
general_section(),
agent_configuration_section(cx),
edit_prediction_language_settings_section(),
edit_prediction_display_sub_section(),
)
.into(),
}
}
fn network_page() -> SettingsPage {
fn network_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SectionHeader("Network"),
SettingsPageItem::SettingItem(SettingItem {
title: "Proxy",
description: "The proxy to use for network requests.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("proxy"),
pick: |settings_content| settings_content.proxy.as_ref(),
write: |settings_content, value, _| {
settings_content.proxy = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
placeholder: Some("socks5h://localhost:10808"),
..Default::default()
})),
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Server URL",
description: "The URL of the Zed server to connect to.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("server_url"),
pick: |settings_content| settings_content.server_url.as_ref(),
write: |settings_content, value, _| {
settings_content.server_url = value;
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
placeholder: Some("https://zed.dev"),
..Default::default()
})),
files: USER,
}),
]
}
SettingsPage {
title: "Network",
items: concat_sections![network_section()],
}
}
fn language_settings_field<T>(
settings_content: &SettingsContent,
get_language_setting_field: fn(&LanguageSettingsContent) -> Option<&T>,
) -> Option<&T> {
let all_languages = &settings_content.project.all_languages;
active_language()
.and_then(|current_language_name| {
all_languages
.languages
.0
.get(current_language_name.as_ref())
})
.and_then(get_language_setting_field)
.or_else(|| get_language_setting_field(&all_languages.defaults))
}
fn language_settings_field_mut<T>(
settings_content: &mut SettingsContent,
value: Option<T>,
write: fn(&mut LanguageSettingsContent, Option<T>),
) {
let all_languages = &mut settings_content.project.all_languages;
let language_content = if let Some(current_language) = active_language() {
all_languages
.languages
.0
.entry(current_language.to_string())
.or_default()
} else {
&mut all_languages.defaults
};
write(language_content, value);
}
fn language_settings_data() -> Box<[SettingsPageItem]> {
fn indentation_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Indentation"),
SettingsPageItem::SettingItem(SettingItem {
title: "Tab Size",
description: "How many columns a tab should occupy.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).tab_size"), // TODO(cameron): not JQ syntax because not URL-safe
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.tab_size.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.tab_size = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Hard Tabs",
description: "Whether to indent lines using tab characters, as opposed to multiple spaces.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).hard_tabs"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.hard_tabs.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.hard_tabs = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Indent",
description: "Controls automatic indentation behavior when typing.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).auto_indent"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.auto_indent.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.auto_indent = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Auto Indent On Paste",
description: "Whether indentation of pasted content should be adjusted based on the context.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).auto_indent_on_paste"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.auto_indent_on_paste.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.auto_indent_on_paste = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn wrapping_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Wrapping"),
SettingsPageItem::SettingItem(SettingItem {
title: "Soft Wrap",
description: "How to soft-wrap long lines of text.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).soft_wrap"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.soft_wrap.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.soft_wrap = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Wrap Guides",
description: "Show wrap guides in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).show_wrap_guides"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.show_wrap_guides.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.show_wrap_guides = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Preferred Line Length",
description: "The column at which to soft-wrap lines, for buffers where soft-wrap is enabled.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).preferred_line_length"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.preferred_line_length.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.preferred_line_length = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Wrap Guides",
description: "Character counts at which to show wrap guides in the editor.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).wrap_guides"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.wrap_guides.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.wrap_guides = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Allow Rewrap",
description: "Controls where the `editor::rewrap` action is allowed for this language.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).allow_rewrap"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.allow_rewrap.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.allow_rewrap = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn indent_guides_section() -> [SettingsPageItem; 6] {
[
SettingsPageItem::SectionHeader("Indent Guides"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Display indent guides in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).indent_guides.enabled"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language
.indent_guides
.as_ref()
.and_then(|indent_guides| indent_guides.enabled.as_ref())
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.indent_guides.get_or_insert_default().enabled = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Line Width",
description: "The width of the indent guides in pixels, between 1 and 10.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).indent_guides.line_width"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language
.indent_guides
.as_ref()
.and_then(|indent_guides| indent_guides.line_width.as_ref())
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.indent_guides.get_or_insert_default().line_width = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Active Line Width",
description: "The width of the active indent guide in pixels, between 1 and 10.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).indent_guides.active_line_width"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language
.indent_guides
.as_ref()
.and_then(|indent_guides| indent_guides.active_line_width.as_ref())
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.indent_guides
.get_or_insert_default()
.active_line_width = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Coloring",
description: "Determines how indent guides are colored.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).indent_guides.coloring"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language
.indent_guides
.as_ref()
.and_then(|indent_guides| indent_guides.coloring.as_ref())
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.indent_guides.get_or_insert_default().coloring = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Background Coloring",
description: "Determines how indent guide backgrounds are colored.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).indent_guides.background_coloring"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.indent_guides.as_ref().and_then(|indent_guides| {
indent_guides.background_coloring.as_ref()
})
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.indent_guides
.get_or_insert_default()
.background_coloring = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn formatting_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Formatting"),
SettingsPageItem::SettingItem(SettingItem {
title: "Format On Save",
description: "On: format the whole buffer.\nOff: do not format.\nModifications: format only lines with unstaged changes; skips formatting when a git diff or LSP range formatting is unavailable.\nModifications If Available: same, but falls back to formatting the whole buffer.",
field: Box::new(
// TODO(settings_ui): this setting should just be a bool
SettingField {
organization_override: None,
json_path: Some("languages.$(language).format_on_save"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.format_on_save.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.format_on_save = value;
},
)
},
},
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Remove Trailing Whitespace On Save",
description: "Whether or not to remove any trailing whitespace from lines of a buffer before saving it.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).remove_trailing_whitespace_on_save"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.remove_trailing_whitespace_on_save.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.remove_trailing_whitespace_on_save = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Ensure Final Newline On Save",
description: "Whether or not to ensure there's a single newline at the end of a buffer when saving it.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).ensure_final_newline_on_save"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.ensure_final_newline_on_save.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.ensure_final_newline_on_save = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Line Ending",
description: "How line endings should be handled for new files and during format and save operations.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).line_ending"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.line_ending.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.line_ending = value;
})
},
}),
metadata: Some(Box::new(SettingsFieldMetadata {
should_do_titlecase: Some(false),
..Default::default()
})),
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Formatter",
description: "How to perform a buffer format.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).formatter"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.formatter.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.formatter = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use On Type Format",
description: "Whether to use additional LSP queries to format (and amend) the code after every \"trigger\" symbol input, defined by LSP server capabilities",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).use_on_type_format"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.use_on_type_format.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.use_on_type_format = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Code Actions On Format",
description: "Additional code actions to run when formatting.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).code_actions_on_format"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.code_actions_on_format.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.code_actions_on_format = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn autoclose_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Autoclose"),
SettingsPageItem::SettingItem(SettingItem {
title: "Use Autoclose",
description: "Whether to automatically type closing characters for you. For example, when you type '(', Zed will automatically add a closing ')' at the correct position.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).use_autoclose"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.use_autoclose.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.use_autoclose = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Use Auto Surround",
description: "Whether to automatically surround text with characters for you. For example, when you select text and type '(', Zed will automatically surround text with ().",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).use_auto_surround"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.use_auto_surround.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.use_auto_surround = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Always Treat Brackets As Autoclosed",
description: "Controls whether the closing characters are always skipped over and auto-removed no matter how they were inserted.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).always_treat_brackets_as_autoclosed"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.always_treat_brackets_as_autoclosed.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.always_treat_brackets_as_autoclosed = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "JSX Tag Auto Close",
description: "Whether to automatically close JSX tags.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).jsx_tag_auto_close"),
// TODO(settings_ui): this setting should just be a bool
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.jsx_tag_auto_close.as_ref()?.enabled.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.jsx_tag_auto_close.get_or_insert_default().enabled = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn whitespace_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Whitespace"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Whitespaces",
description: "Whether to show tabs and spaces in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).show_whitespaces"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.show_whitespaces.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.show_whitespaces = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Space Whitespace Indicator",
description: "Visible character used to render space characters when show_whitespaces is enabled (default: \"•\")",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).whitespace_map.space"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.whitespace_map.as_ref()?.space.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.whitespace_map.get_or_insert_default().space = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Tab Whitespace Indicator",
description: "Visible character used to render tab characters when show_whitespaces is enabled (default: \"→\")",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).whitespace_map.tab"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.whitespace_map.as_ref()?.tab.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.whitespace_map.get_or_insert_default().tab = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn completions_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Completions"),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Completions On Input",
description: "Whether to pop the completions menu while typing in an editor without explicitly requesting it.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).show_completions_on_input"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.show_completions_on_input.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.show_completions_on_input = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Completion Documentation",
description: "Whether to display inline and alongside documentation for items in the completions menu.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).show_completion_documentation"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.show_completion_documentation.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.show_completion_documentation = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Words",
description: "Controls how words are completed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).completions.words"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.completions.as_ref()?.words.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.completions.get_or_insert_default().words = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Words Min Length",
description: "How many characters has to be in the completions query to automatically show the words-based completions.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).completions.words_min_length"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.completions.as_ref()?.words_min_length.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.completions
.get_or_insert_default()
.words_min_length = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Completion Menu Scrollbar",
description: "When to show the scrollbar in the completion menu.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("editor.completion_menu_scrollbar"),
pick: |settings_content| {
settings_content.editor.completion_menu_scrollbar.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.completion_menu_scrollbar = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Completion Detail Alignment",
description: "Whether to align detail text in code completions context menus left or right.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("editor.completion_detail_alignment"),
pick: |settings_content| {
settings_content.editor.completion_detail_alignment.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.completion_detail_alignment = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Completion Menu Item Kind",
description: "How to display the LSP item kind (function, method, variable, etc.) of each entry in the completions menu.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("editor.completion_menu_item_kind"),
pick: |settings_content| {
settings_content.editor.completion_menu_item_kind.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.completion_menu_item_kind = value;
},
}),
metadata: None,
files: USER,
}),
]
}
fn inlay_hints_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("Inlay Hints"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Global switch to toggle hints on and off.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.enabled"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.enabled.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.inlay_hints.get_or_insert_default().enabled = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Value Hints",
description: "Global switch to toggle inline values on and off when debugging.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.show_value_hints"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.show_value_hints.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.inlay_hints
.get_or_insert_default()
.show_value_hints = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Type Hints",
description: "Whether type hints should be shown.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.show_type_hints"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.show_type_hints.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.inlay_hints.get_or_insert_default().show_type_hints = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Parameter Hints",
description: "Whether parameter hints should be shown.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.show_parameter_hints"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.show_parameter_hints.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.inlay_hints
.get_or_insert_default()
.show_parameter_hints = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Other Hints",
description: "Whether other hints should be shown.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.show_other_hints"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.show_other_hints.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.inlay_hints
.get_or_insert_default()
.show_other_hints = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Background",
description: "Show a background for inlay hints.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.show_background"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.show_background.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.inlay_hints.get_or_insert_default().show_background = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Edit Debounce Ms",
description: "Whether or not to debounce inlay hints updates after buffer edits (set to 0 to disable debouncing).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.edit_debounce_ms"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.edit_debounce_ms.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.inlay_hints
.get_or_insert_default()
.edit_debounce_ms = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Debounce Ms",
description: "Whether or not to debounce inlay hints updates after buffer scrolls (set to 0 to disable debouncing).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).inlay_hints.scroll_debounce_ms"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.inlay_hints.as_ref()?.scroll_debounce_ms.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.inlay_hints
.get_or_insert_default()
.scroll_debounce_ms = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Toggle On Modifiers Press",
description: "Toggles inlay hints (hides or shows) when the user presses the modifiers specified.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some(
"languages.$(language).inlay_hints.toggle_on_modifiers_press",
),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language
.inlay_hints
.as_ref()?
.toggle_on_modifiers_press
.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language
.inlay_hints
.get_or_insert_default()
.toggle_on_modifiers_press = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn tasks_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("Tasks"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Whether tasks are enabled for this language.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).tasks.enabled"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.tasks.as_ref()?.enabled.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.tasks.get_or_insert_default().enabled = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Variables",
description: "Extra task variables to set for a particular language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).tasks.variables"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.tasks.as_ref()?.variables.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.tasks.get_or_insert_default().variables = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Prefer LSP",
description: "Use LSP tasks over Zed language extension tasks.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).tasks.prefer_lsp"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.tasks.as_ref()?.prefer_lsp.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.tasks.get_or_insert_default().prefer_lsp = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn miscellaneous_section() -> [SettingsPageItem; 8] {
[
SettingsPageItem::SectionHeader("Miscellaneous"),
SettingsPageItem::SettingItem(SettingItem {
title: "Language Detection",
description: "Whether to enable automatic language detection in unsaved buffers.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("language_detection"),
pick: |settings_content| settings_content.editor.language_detection.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.language_detection = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Word Diff Enabled",
description: "Whether to enable word diff highlighting in the editor. When enabled, changed words within modified lines are highlighted to show exactly what changed.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).word_diff_enabled"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.word_diff_enabled.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.word_diff_enabled = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Debuggers",
description: "Preferred debuggers for this language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).debuggers"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.debuggers.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.debuggers = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Middle Click Paste",
description: "Enable middle-click paste on Linux.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).editor.middle_click_paste"),
pick: |settings_content| settings_content.editor.middle_click_paste.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.middle_click_paste = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Extend Comment On Newline",
description: "Whether to start a new line with a comment when a previous line is a comment as well.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).extend_comment_on_newline"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.extend_comment_on_newline.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.extend_comment_on_newline = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Colorize Brackets",
description: "Whether to colorize brackets in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).colorize_brackets"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.colorize_brackets.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.colorize_brackets = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Vim/Emacs Modeline Support",
description: "Number of lines to search for modelines (set to 0 to disable).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("modeline_lines"),
pick: |settings_content| settings_content.modeline_lines.as_ref(),
write: |settings_content, value, _| {
settings_content.modeline_lines = value;
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn global_only_miscellaneous_sub_section() -> [SettingsPageItem; 3] {
[
SettingsPageItem::SettingItem(SettingItem {
title: "Image Viewer",
description: "The unit for image file sizes.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("image_viewer.unit"),
pick: |settings_content| {
settings_content
.image_viewer
.as_ref()
.and_then(|image_viewer| image_viewer.unit.as_ref())
},
write: |settings_content, value, _| {
settings_content.image_viewer.get_or_insert_default().unit = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::DynamicItem(DynamicItem {
discriminant: SettingItem {
files: USER,
title: "Limit Markdown Preview Width",
description: "Whether to constrain the markdown preview content to a maximum width, centering it when the pane is wider, for optimal readability.",
field: Box::new(SettingField::<bool> {
organization_override: None,
json_path: Some("markdown_preview.limit_content_width"),
pick: |settings_content| {
settings_content
.markdown_preview
.as_ref()?
.limit_content_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.markdown_preview
.get_or_insert_default()
.limit_content_width = value;
},
}),
metadata: None,
},
pick_discriminant: |settings_content| {
let enabled = settings_content
.markdown_preview
.as_ref()?
.limit_content_width
.unwrap_or(true);
Some(if enabled { 1 } else { 0 })
},
fields: vec![
vec![],
vec![SettingItem {
files: USER,
title: "Max Width",
description: "Maximum content width in pixels. Content will be centered when the pane is wider than this value.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("markdown_preview.max_width"),
pick: |settings_content| {
settings_content
.markdown_preview
.as_ref()?
.max_width
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.markdown_preview
.get_or_insert_default()
.max_width = value;
},
}),
metadata: None,
}],
],
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Drop Size Target",
description: "Relative size of the drop target in the editor that will open dropped file as a split pane.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("drop_target_size"),
pick: |settings_content| settings_content.workspace.drop_target_size.as_ref(),
write: |settings_content, value, _| {
settings_content.workspace.drop_target_size = value;
},
}),
metadata: None,
files: USER,
}),
]
}
let is_global = active_language().is_none();
let code_lens_item = [SettingsPageItem::SettingItem(SettingItem {
title: "Code Lens",
description: "Whether and how to display code lenses from language servers.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("code_lens"),
pick: |settings_content| settings_content.editor.code_lens.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.code_lens = value;
},
}),
metadata: None,
files: USER,
})];
let lsp_document_colors_item = [SettingsPageItem::SettingItem(SettingItem {
title: "LSP Document Colors",
description: "How to render LSP color previews in the editor.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("lsp_document_colors"),
pick: |settings_content| settings_content.editor.lsp_document_colors.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.lsp_document_colors = value;
},
}),
metadata: None,
files: USER,
})];
if is_global {
concat_sections!(
indentation_section(),
wrapping_section(),
indent_guides_section(),
formatting_section(),
autoclose_section(),
whitespace_section(),
completions_section(),
inlay_hints_section(),
code_lens_item,
lsp_document_colors_item,
tasks_section(),
miscellaneous_section(),
global_only_miscellaneous_sub_section(),
)
} else {
concat_sections!(
indentation_section(),
wrapping_section(),
indent_guides_section(),
formatting_section(),
autoclose_section(),
whitespace_section(),
completions_section(),
inlay_hints_section(),
code_lens_item,
tasks_section(),
miscellaneous_section(),
)
}
}
/// LanguageSettings items that should be included in the "Languages & Tools" page
/// not the "Editor" page
fn non_editor_language_settings_data() -> Box<[SettingsPageItem]> {
fn lsp_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("LSP"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enable Language Server",
description: "Whether to use language servers to provide code intelligence.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).enable_language_server"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.enable_language_server.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.enable_language_server = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Language Servers",
description: "The list of language servers to use (or disable) for this language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).language_servers"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.language_servers.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.language_servers = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Linked Edits",
description: "Whether to perform linked edits of associated ranges, if the LS supports it. For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).linked_edits"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.linked_edits.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.linked_edits = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Go To Definition Fallback",
description: "Whether to follow-up empty Go to definition responses from the language server.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("go_to_definition_fallback"),
pick: |settings_content| {
settings_content.editor.go_to_definition_fallback.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.go_to_definition_fallback = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Go To Definition Scroll Strategy",
description: "How to scroll the target into view when navigating to a definition or reference.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("go_to_definition_scroll_strategy"),
pick: |settings_content| {
settings_content
.editor
.go_to_definition_scroll_strategy
.as_ref()
},
write: |settings_content, value, _| {
settings_content.editor.go_to_definition_scroll_strategy = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "LSP Results Location",
description: "Where to show LSP results that can contain multiple locations (Go to Definition, Go to Implementation, Find All References).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("lsp_results_location"),
pick: |settings_content| settings_content.editor.lsp_results_location.as_ref(),
write: |settings_content, value, _| {
settings_content.editor.lsp_results_location = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Semantic Tokens",
description: {
static DESCRIPTION: OnceLock<&'static str> = OnceLock::new();
DESCRIPTION.get_or_init(|| {
SemanticTokens::VARIANTS
.iter()
.filter_map(|v| {
v.get_documentation().map(|doc| format!("{v:?}: {doc}"))
})
.join("\n")
.leak()
})
},
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).semantic_tokens"),
pick: |settings_content| {
settings_content
.project
.all_languages
.defaults
.semantic_tokens
.as_ref()
},
write: |settings_content, value, _| {
settings_content
.project
.all_languages
.defaults
.semantic_tokens = value;
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "LSP Folding Ranges",
description: "When enabled, use folding ranges from the language server instead of indent-based folding.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).document_folding_ranges"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.document_folding_ranges.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.document_folding_ranges = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "LSP Document Symbols",
description: "When enabled, use the language server's document symbols for outlines and breadcrumbs instead of tree-sitter.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).document_symbols"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.document_symbols.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.document_symbols = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn lsp_completions_section() -> [SettingsPageItem; 4] {
[
SettingsPageItem::SectionHeader("LSP Completions"),
SettingsPageItem::SettingItem(SettingItem {
title: "Enabled",
description: "Whether to fetch LSP completions or not.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).completions.lsp"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.completions.as_ref()?.lsp.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.completions.get_or_insert_default().lsp = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Fetch Timeout (milliseconds)",
description: "When fetching LSP completions, determines how long to wait for a response of a particular server (set to 0 to wait indefinitely).",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).completions.lsp_fetch_timeout_ms"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.completions.as_ref()?.lsp_fetch_timeout_ms.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language
.completions
.get_or_insert_default()
.lsp_fetch_timeout_ms = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Insert Mode",
description: "Controls how LSP completions are inserted.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).completions.lsp_insert_mode"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.completions.as_ref()?.lsp_insert_mode.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.completions.get_or_insert_default().lsp_insert_mode = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn debugger_section() -> [SettingsPageItem; 2] {
[
SettingsPageItem::SectionHeader("Debuggers"),
SettingsPageItem::SettingItem(SettingItem {
title: "Debuggers",
description: "Preferred debuggers for this language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).debuggers"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.debuggers.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.debuggers = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn prettier_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Prettier"),
SettingsPageItem::SettingItem(SettingItem {
title: "Allowed",
description: "Enables or disables formatting with Prettier for a given language.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).prettier.allowed"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.prettier.as_ref()?.allowed.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.prettier.get_or_insert_default().allowed = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Parser",
description: "Forces Prettier integration to use a specific parser name when formatting files with the language.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).prettier.parser"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.prettier.as_ref()?.parser.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.prettier.get_or_insert_default().parser = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Plugins",
description: "Forces Prettier integration to use specific plugins when formatting files with the language.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).prettier.plugins"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.prettier.as_ref()?.plugins.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.prettier.get_or_insert_default().plugins = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Options",
description: "Default Prettier options, in the format as in package.json section for Prettier.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).prettier.options"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.prettier.as_ref()?.options.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(
settings_content,
value,
|language, value| {
language.prettier.get_or_insert_default().options = value;
},
)
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
concat_sections!(
lsp_section(),
lsp_completions_section(),
debugger_section(),
prettier_section(),
)
}
fn edit_prediction_language_settings_section() -> [SettingsPageItem; 5] {
[
SettingsPageItem::SectionHeader("Edit Predictions"),
SettingsPageItem::SubPageLink(SubPageLink {
title: "Configure Providers".into(),
r#type: Default::default(),
json_path: Some("edit_predictions.providers"),
description: Some("Set up different edit prediction providers in complement to Zed's built-in Zeta model.".into()),
search_aliases: &[],
in_json: false,
files: USER,
render: render_edit_prediction_setup_page
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Data Collection",
description: "Controls whether Zed may collect training data when using Zed's Edit Predictions. Data is only collected for files in projects detected as open source. The default value uses the preference previously set via the status-bar toggle, or false if no preference has been stored.",
field: Box::new(SettingField {
organization_override: Some(|org_settings| {
const DATA_COLLECTION_DISABLED: EditPredictionDataCollectionChoice = EditPredictionDataCollectionChoice::No;
if !org_settings.edit_prediction.is_feedback_enabled {
Some(&DATA_COLLECTION_DISABLED)
} else {
None
}
}),
json_path: Some("edit_predictions.allow_data_collection"),
pick: |settings_content| {
settings_content
.project
.all_languages
.edit_predictions
.as_ref()?
.allow_data_collection
.as_ref()
},
write: |settings_content, value, _app| {
settings_content
.project
.all_languages
.edit_predictions
.get_or_insert_default()
.allow_data_collection = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Show Edit Predictions",
description: "Controls whether edit predictions are shown immediately or manually.",
field: Box::new(SettingField {
organization_override: None,
json_path: Some("languages.$(language).show_edit_predictions"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.show_edit_predictions.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.show_edit_predictions = value;
})
},
}),
metadata: None,
files: USER | PROJECT,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Disable in Language Scopes",
description: "Controls whether edit predictions are shown in the given language scopes.",
field: Box::new(
SettingField {
organization_override: None,
json_path: Some("languages.$(language).edit_predictions_disabled_in"),
pick: |settings_content| {
language_settings_field(settings_content, |language| {
language.edit_predictions_disabled_in.as_ref()
})
},
write: |settings_content, value, _| {
language_settings_field_mut(settings_content, value, |language, value| {
language.edit_predictions_disabled_in = value;
})
},
}
.unimplemented(),
),
metadata: None,
files: USER | PROJECT,
}),
]
}
fn show_scrollbar_or_editor(
settings_content: &SettingsContent,
show: fn(&SettingsContent) -> Option<&settings::ShowScrollbar>,
) -> Option<&settings::ShowScrollbar> {
show(settings_content).or(settings_content
.editor
.scrollbar
.as_ref()
.and_then(|scrollbar| scrollbar.show.as_ref()))
}
fn dynamic_variants<T>() -> &'static [T::Discriminant]
where
T: strum::IntoDiscriminant,
T::Discriminant: strum::VariantArray,
{
<<T as strum::IntoDiscriminant>::Discriminant as strum::VariantArray>::VARIANTS
}
/// Updates the `vim_mode` setting, disabling `helix_mode` if present and
/// `vim_mode` is being enabled.
fn write_vim_mode(settings: &mut SettingsContent, value: Option<bool>, _: &App) {
write_vim_mode_inner(settings, value);
}
fn write_vim_mode_inner(settings: &mut SettingsContent, value: Option<bool>) {
if value == Some(true) && settings.helix_mode == Some(true) {
settings.helix_mode = Some(false);
}
settings.vim_mode = value;
}
/// Updates the `helix_mode` setting, disabling `vim_mode` if present and
/// `helix_mode` is being enabled.
fn write_helix_mode(settings: &mut SettingsContent, value: Option<bool>, _: &App) {
write_helix_mode_inner(settings, value);
}
fn write_helix_mode_inner(settings: &mut SettingsContent, value: Option<bool>) {
if value == Some(true) && settings.vim_mode == Some(true) {
settings.vim_mode = Some(false);
}
settings.helix_mode = value;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_write_vim_helix_mode() {
// Enabling vim mode while `vim_mode` and `helix_mode` are not yet set
// should only update the `vim_mode` setting.
let mut settings = SettingsContent::default();
write_vim_mode_inner(&mut settings, Some(true));
assert_eq!(settings.vim_mode, Some(true));
assert_eq!(settings.helix_mode, None);
// Enabling helix mode while `vim_mode` and `helix_mode` are not yet set
// should only update the `helix_mode` setting.
let mut settings = SettingsContent::default();
write_helix_mode_inner(&mut settings, Some(true));
assert_eq!(settings.helix_mode, Some(true));
assert_eq!(settings.vim_mode, None);
// Disabling helix mode should only touch `helix_mode` setting when
// `vim_mode` is not set.
write_helix_mode_inner(&mut settings, Some(false));
assert_eq!(settings.helix_mode, Some(false));
assert_eq!(settings.vim_mode, None);
// Enabling vim mode should update `vim_mode` but leave `helix_mode`
// untouched.
write_vim_mode_inner(&mut settings, Some(true));
assert_eq!(settings.vim_mode, Some(true));
assert_eq!(settings.helix_mode, Some(false));
// Enabling helix mode should update `helix_mode` and disable
// `vim_mode`.
write_helix_mode_inner(&mut settings, Some(true));
assert_eq!(settings.helix_mode, Some(true));
assert_eq!(settings.vim_mode, Some(false));
// Enabling vim mode should update `vim_mode` and disable
// `helix_mode`.
write_vim_mode_inner(&mut settings, Some(true));
assert_eq!(settings.vim_mode, Some(true));
assert_eq!(settings.helix_mode, Some(false));
}
}