use std::sync::Arc; use gpui::{App, FontFeatures, FontWeight};use project::project_settings::{InlineBlameSettings, ProjectSettings};use settings::{EditableSettingControl, Settings};use theme::{FontFamilyCache, ThemeSettings};use ui::{ CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup, prelude::*,}; use crate::EditorSettings; #[derive(IntoElement)]pub struct EditorSettingsControls {} impl Default for EditorSettingsControls { fn default() -> Self { Self::new() }} impl EditorSettingsControls { pub fn new() -> Self { Self {} }} impl RenderOnce for EditorSettingsControls { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { SettingsContainer::new() .child( SettingsGroup::new("Font") .child( h_flex() .gap_2() .justify_between() .child(BufferFontFamilyControl) .child(BufferFontWeightControl), ) .child(BufferFontSizeControl) .child(BufferFontLigaturesControl), ) .child(SettingsGroup::new("Editor").child(InlineGitBlameControl)) .child( SettingsGroup::new("Gutter").child( h_flex() .gap_2() .justify_between() .child(LineNumbersControl) .child(RelativeLineNumbersControl), ), ) }} #[derive(IntoElement)]struct BufferFontFamilyControl; impl EditableSettingControl for BufferFontFamilyControl { type Value = SharedString; type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Family".into() } fn read(cx: &App) -> Self::Value { let settings = ThemeSettings::get_global(cx); settings.buffer_font.family.clone() } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { settings.buffer_font_family = Some(value.to_string()); }} impl RenderOnce for BufferFontFamilyControl { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); h_flex() .gap_2() .child(Icon::new(IconName::Font)) .child(DropdownMenu::new( "buffer-font-family", value.clone(), ContextMenu::build(window, cx, |mut menu, _, cx| { let font_family_cache = FontFamilyCache::global(cx); for font_name in font_family_cache.list_font_families(cx) { menu = menu.custom_entry( { let font_name = font_name.clone(); move |_window, _cx| Label::new(font_name.clone()).into_any_element() }, { let font_name = font_name.clone(); move |_window, cx| { Self::write(font_name.clone(), cx); } }, ) } menu }), )) }} #[derive(IntoElement)]struct BufferFontSizeControl; impl EditableSettingControl for BufferFontSizeControl { type Value = Pixels; type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Size".into() } fn read(cx: &App) -> Self::Value { ThemeSettings::get_global(cx).buffer_font_size(cx) } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { settings.buffer_font_size = Some(value.into()); }} impl RenderOnce for BufferFontSizeControl { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); h_flex() .gap_2() .child(Icon::new(IconName::FontSize)) .child(NumericStepper::new( "buffer-font-size", value.to_string(), move |_, _, cx| { Self::write(value - px(1.), cx); }, move |_, _, cx| { Self::write(value + px(1.), cx); }, )) }} #[derive(IntoElement)]struct BufferFontWeightControl; impl EditableSettingControl for BufferFontWeightControl { type Value = FontWeight; type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Weight".into() } fn read(cx: &App) -> Self::Value { let settings = ThemeSettings::get_global(cx); settings.buffer_font.weight } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { settings.buffer_font_weight = Some(value.0); }} impl RenderOnce for BufferFontWeightControl { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); h_flex() .gap_2() .child(Icon::new(IconName::FontWeight)) .child(DropdownMenu::new( "buffer-font-weight", value.0.to_string(), ContextMenu::build(window, cx, |mut menu, _window, _cx| { for weight in FontWeight::ALL { menu = menu.custom_entry( move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(), { move |_, cx| { Self::write(weight, cx); } }, ) } menu }), )) }} #[derive(IntoElement)]struct BufferFontLigaturesControl; impl EditableSettingControl for BufferFontLigaturesControl { type Value = bool; type Settings = ThemeSettings; fn name(&self) -> SharedString { "Buffer Font Ligatures".into() } fn read(cx: &App) -> Self::Value { let settings = ThemeSettings::get_global(cx); settings .buffer_font .features .is_calt_enabled() .unwrap_or(true) } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { let value = if value { 1 } else { 0 }; let mut features = settings .buffer_font_features .as_ref() .map(|features| features.tag_value_list().to_vec()) .unwrap_or_default(); if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") { features[calt_index].1 = value; } else { features.push(("calt".into(), value)); } settings.buffer_font_features = Some(FontFeatures(Arc::new(features))); }} impl RenderOnce for BufferFontLigaturesControl { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); CheckboxWithLabel::new( "buffer-font-ligatures", Label::new(self.name()), value.into(), |selection, _, cx| { Self::write( match selection { ToggleState::Selected => true, ToggleState::Unselected | ToggleState::Indeterminate => false, }, cx, ); }, ) }} #[derive(IntoElement)]struct InlineGitBlameControl; impl EditableSettingControl for InlineGitBlameControl { type Value = bool; type Settings = ProjectSettings; fn name(&self) -> SharedString { "Inline Git Blame".into() } fn read(cx: &App) -> Self::Value { let settings = ProjectSettings::get_global(cx); settings.git.inline_blame_enabled() } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { if let Some(inline_blame) = settings.git.inline_blame.as_mut() { inline_blame.enabled = value; } else { settings.git.inline_blame = Some(InlineBlameSettings { enabled: false, ..Default::default() }); } }} impl RenderOnce for InlineGitBlameControl { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); CheckboxWithLabel::new( "inline-git-blame", Label::new(self.name()), value.into(), |selection, _, cx| { Self::write( match selection { ToggleState::Selected => true, ToggleState::Unselected | ToggleState::Indeterminate => false, }, cx, ); }, ) }} #[derive(IntoElement)]struct LineNumbersControl; impl EditableSettingControl for LineNumbersControl { type Value = bool; type Settings = EditorSettings; fn name(&self) -> SharedString { "Line Numbers".into() } fn read(cx: &App) -> Self::Value { let settings = EditorSettings::get_global(cx); settings.gutter.line_numbers } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { if let Some(gutter) = settings.gutter.as_mut() { gutter.line_numbers = Some(value); } else { settings.gutter = Some(crate::editor_settings::GutterContent { line_numbers: Some(value), ..Default::default() }); } }} impl RenderOnce for LineNumbersControl { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); CheckboxWithLabel::new( "line-numbers", Label::new(self.name()), value.into(), |selection, _, cx| { Self::write( match selection { ToggleState::Selected => true, ToggleState::Unselected | ToggleState::Indeterminate => false, }, cx, ); }, ) }} #[derive(IntoElement)]struct RelativeLineNumbersControl; impl EditableSettingControl for RelativeLineNumbersControl { type Value = bool; type Settings = EditorSettings; fn name(&self) -> SharedString { "Relative Line Numbers".into() } fn read(cx: &App) -> Self::Value { let settings = EditorSettings::get_global(cx); settings.relative_line_numbers } fn apply( settings: &mut <Self::Settings as Settings>::FileContent, value: Self::Value, _cx: &App, ) { settings.relative_line_numbers = Some(value); }} impl RenderOnce for RelativeLineNumbersControl { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { let value = Self::read(cx); DropdownMenu::new( "relative-line-numbers", if value { "Relative" } else { "Ascending" }, ContextMenu::build(window, cx, |menu, _window, _cx| { menu.custom_entry( |_window, _cx| Label::new("Ascending").into_any_element(), move |_, cx| Self::write(false, cx), ) .custom_entry( |_window, _cx| Label::new("Relative").into_any_element(), move |_, cx| Self::write(true, cx), ) }), ) }}
use std::sync::Arc;
use gpui::{App, FontFeatures, FontWeight};
use project::project_settings::{InlineBlameSettings, ProjectSettings};
use settings::{EditableSettingControl, Settings};
use theme::{FontFamilyCache, ThemeSettings};
use ui::{
CheckboxWithLabel, ContextMenu, DropdownMenu, NumericStepper, SettingsContainer, SettingsGroup,
prelude::*,
};
use crate::EditorSettings;
#[derive(IntoElement)]
pub struct EditorSettingsControls {}
impl Default for EditorSettingsControls {
fn default() -> Self {
Self::new()
}
}
impl EditorSettingsControls {
pub fn new() -> Self {
Self {}
}
}
impl RenderOnce for EditorSettingsControls {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
SettingsContainer::new()
.child(
SettingsGroup::new("Font")
.child(
h_flex()
.gap_2()
.justify_between()
.child(BufferFontFamilyControl)
.child(BufferFontWeightControl),
)
.child(BufferFontSizeControl)
.child(BufferFontLigaturesControl),
)
.child(SettingsGroup::new("Editor").child(InlineGitBlameControl))
.child(
SettingsGroup::new("Gutter").child(
h_flex()
.gap_2()
.justify_between()
.child(LineNumbersControl)
.child(RelativeLineNumbersControl),
),
)
}
}
#[derive(IntoElement)]
struct BufferFontFamilyControl;
impl EditableSettingControl for BufferFontFamilyControl {
type Value = SharedString;
type Settings = ThemeSettings;
fn name(&self) -> SharedString {
"Buffer Font Family".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings.buffer_font.family.clone()
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
settings.buffer_font_family = Some(value.to_string());
}
}
impl RenderOnce for BufferFontFamilyControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::Font))
.child(DropdownMenu::new(
"buffer-font-family",
value.clone(),
ContextMenu::build(window, cx, |mut menu, _, cx| {
let font_family_cache = FontFamilyCache::global(cx);
for font_name in font_family_cache.list_font_families(cx) {
menu = menu.custom_entry(
{
let font_name = font_name.clone();
move |_window, _cx| Label::new(font_name.clone()).into_any_element()
},
{
let font_name = font_name.clone();
move |_window, cx| {
Self::write(font_name.clone(), cx);
}
},
)
}
menu
}),
))
}
}
#[derive(IntoElement)]
struct BufferFontSizeControl;
impl EditableSettingControl for BufferFontSizeControl {
type Value = Pixels;
type Settings = ThemeSettings;
fn name(&self) -> SharedString {
"Buffer Font Size".into()
}
fn read(cx: &App) -> Self::Value {
ThemeSettings::get_global(cx).buffer_font_size(cx)
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
settings.buffer_font_size = Some(value.into());
}
}
impl RenderOnce for BufferFontSizeControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::FontSize))
.child(NumericStepper::new(
"buffer-font-size",
value.to_string(),
move |_, _, cx| {
Self::write(value - px(1.), cx);
},
move |_, _, cx| {
Self::write(value + px(1.), cx);
},
))
}
}
#[derive(IntoElement)]
struct BufferFontWeightControl;
impl EditableSettingControl for BufferFontWeightControl {
type Value = FontWeight;
type Settings = ThemeSettings;
fn name(&self) -> SharedString {
"Buffer Font Weight".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings.buffer_font.weight
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
settings.buffer_font_weight = Some(value.0);
}
}
impl RenderOnce for BufferFontWeightControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
h_flex()
.gap_2()
.child(Icon::new(IconName::FontWeight))
.child(DropdownMenu::new(
"buffer-font-weight",
value.0.to_string(),
ContextMenu::build(window, cx, |mut menu, _window, _cx| {
for weight in FontWeight::ALL {
menu = menu.custom_entry(
move |_window, _cx| Label::new(weight.0.to_string()).into_any_element(),
{
move |_, cx| {
Self::write(weight, cx);
}
},
)
}
menu
}),
))
}
}
#[derive(IntoElement)]
struct BufferFontLigaturesControl;
impl EditableSettingControl for BufferFontLigaturesControl {
type Value = bool;
type Settings = ThemeSettings;
fn name(&self) -> SharedString {
"Buffer Font Ligatures".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ThemeSettings::get_global(cx);
settings
.buffer_font
.features
.is_calt_enabled()
.unwrap_or(true)
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
let value = if value { 1 } else { 0 };
let mut features = settings
.buffer_font_features
.as_ref()
.map(|features| features.tag_value_list().to_vec())
.unwrap_or_default();
if let Some(calt_index) = features.iter().position(|(tag, _)| tag == "calt") {
features[calt_index].1 = value;
} else {
features.push(("calt".into(), value));
}
settings.buffer_font_features = Some(FontFeatures(Arc::new(features)));
}
}
impl RenderOnce for BufferFontLigaturesControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"buffer-font-ligatures",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct InlineGitBlameControl;
impl EditableSettingControl for InlineGitBlameControl {
type Value = bool;
type Settings = ProjectSettings;
fn name(&self) -> SharedString {
"Inline Git Blame".into()
}
fn read(cx: &App) -> Self::Value {
let settings = ProjectSettings::get_global(cx);
settings.git.inline_blame_enabled()
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
inline_blame.enabled = value;
} else {
settings.git.inline_blame = Some(InlineBlameSettings {
enabled: false,
..Default::default()
});
}
}
}
impl RenderOnce for InlineGitBlameControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"inline-git-blame",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct LineNumbersControl;
impl EditableSettingControl for LineNumbersControl {
type Value = bool;
type Settings = EditorSettings;
fn name(&self) -> SharedString {
"Line Numbers".into()
}
fn read(cx: &App) -> Self::Value {
let settings = EditorSettings::get_global(cx);
settings.gutter.line_numbers
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
if let Some(gutter) = settings.gutter.as_mut() {
gutter.line_numbers = Some(value);
} else {
settings.gutter = Some(crate::editor_settings::GutterContent {
line_numbers: Some(value),
..Default::default()
});
}
}
}
impl RenderOnce for LineNumbersControl {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
CheckboxWithLabel::new(
"line-numbers",
Label::new(self.name()),
value.into(),
|selection, _, cx| {
Self::write(
match selection {
ToggleState::Selected => true,
ToggleState::Unselected | ToggleState::Indeterminate => false,
},
cx,
);
},
)
}
}
#[derive(IntoElement)]
struct RelativeLineNumbersControl;
impl EditableSettingControl for RelativeLineNumbersControl {
type Value = bool;
type Settings = EditorSettings;
fn name(&self) -> SharedString {
"Relative Line Numbers".into()
}
fn read(cx: &App) -> Self::Value {
let settings = EditorSettings::get_global(cx);
settings.relative_line_numbers
}
fn apply(
settings: &mut <Self::Settings as Settings>::FileContent,
value: Self::Value,
_cx: &App,
) {
settings.relative_line_numbers = Some(value);
}
}
impl RenderOnce for RelativeLineNumbersControl {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let value = Self::read(cx);
DropdownMenu::new(
"relative-line-numbers",
if value { "Relative" } else { "Ascending" },
ContextMenu::build(window, cx, |menu, _window, _cx| {
menu.custom_entry(
|_window, _cx| Label::new("Ascending").into_any_element(),
move |_, cx| Self::write(false, cx),
)
.custom_entry(
|_window, _cx| Label::new("Relative").into_any_element(),
move |_, cx| Self::write(true, cx),
)
}),
)
}
}