terminal_settings.rs

  1use alacritty_terminal::vte::ansi::{
  2    CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
  3};
  4use collections::HashMap;
  5use gpui::{FontFallbacks, FontFeatures, FontWeight, Pixels, px};
  6use schemars::JsonSchema;
  7use serde::{Deserialize, Serialize};
  8
  9pub use settings::AlternateScroll;
 10
 11use settings::{
 12    PathHyperlinkRegex, RegisterSetting, ShowScrollbar, TerminalBlink, TerminalDockPosition,
 13    TerminalLineHeight, VenvSettings, WorkingDirectory, merge_from::MergeFrom,
 14};
 15use task::Shell;
 16use theme::FontFamilyName;
 17
 18#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 19pub struct Toolbar {
 20    pub breadcrumbs: bool,
 21}
 22
 23#[derive(Clone, Debug, Deserialize, RegisterSetting)]
 24pub struct TerminalSettings {
 25    pub shell: Shell,
 26    pub working_directory: WorkingDirectory,
 27    pub font_size: Option<Pixels>, // todo(settings_refactor) can be non-optional...
 28    pub font_family: Option<FontFamilyName>,
 29    pub font_fallbacks: Option<FontFallbacks>,
 30    pub font_features: Option<FontFeatures>,
 31    pub font_weight: Option<FontWeight>,
 32    pub line_height: TerminalLineHeight,
 33    pub env: HashMap<String, String>,
 34    pub cursor_shape: CursorShape,
 35    pub blinking: TerminalBlink,
 36    pub alternate_scroll: AlternateScroll,
 37    pub option_as_meta: bool,
 38    pub copy_on_select: bool,
 39    pub keep_selection_on_copy: bool,
 40    pub button: bool,
 41    pub dock: TerminalDockPosition,
 42    pub default_width: Pixels,
 43    pub default_height: Pixels,
 44    pub detect_venv: VenvSettings,
 45    pub max_scroll_history_lines: Option<usize>,
 46    pub scroll_multiplier: f32,
 47    pub toolbar: Toolbar,
 48    pub scrollbar: ScrollbarSettings,
 49    pub minimum_contrast: f32,
 50    pub path_hyperlink_regexes: Vec<String>,
 51    pub path_hyperlink_timeout_ms: u64,
 52}
 53
 54#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 55pub struct ScrollbarSettings {
 56    /// When to show the scrollbar in the terminal.
 57    ///
 58    /// Default: inherits editor scrollbar settings
 59    pub show: Option<ShowScrollbar>,
 60}
 61
 62fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell {
 63    match shell {
 64        settings::Shell::System => Shell::System,
 65        settings::Shell::Program(program) => Shell::Program(program),
 66        settings::Shell::WithArguments {
 67            program,
 68            args,
 69            title_override,
 70        } => Shell::WithArguments {
 71            program,
 72            args,
 73            title_override: title_override.map(Into::into),
 74        },
 75    }
 76}
 77
 78impl settings::Settings for TerminalSettings {
 79    fn from_settings(content: &settings::SettingsContent) -> Self {
 80        let user_content = content.terminal.clone().unwrap();
 81        // Note: we allow a subset of "terminal" settings in the project files.
 82        let mut project_content = user_content.project.clone();
 83        project_content.merge_from_option(content.project.terminal.as_ref());
 84        TerminalSettings {
 85            shell: settings_shell_to_task_shell(project_content.shell.unwrap()),
 86            working_directory: project_content.working_directory.unwrap(),
 87            font_size: user_content.font_size.map(px),
 88            font_family: user_content.font_family,
 89            font_fallbacks: user_content.font_fallbacks.map(|fallbacks| {
 90                FontFallbacks::from_fonts(
 91                    fallbacks
 92                        .into_iter()
 93                        .map(|family| family.0.to_string())
 94                        .collect(),
 95                )
 96            }),
 97            font_features: user_content.font_features,
 98            font_weight: user_content.font_weight.map(FontWeight),
 99            line_height: user_content.line_height.unwrap(),
100            env: project_content.env.unwrap(),
101            cursor_shape: user_content.cursor_shape.unwrap().into(),
102            blinking: user_content.blinking.unwrap(),
103            alternate_scroll: user_content.alternate_scroll.unwrap(),
104            option_as_meta: user_content.option_as_meta.unwrap(),
105            copy_on_select: user_content.copy_on_select.unwrap(),
106            keep_selection_on_copy: user_content.keep_selection_on_copy.unwrap(),
107            button: user_content.button.unwrap(),
108            dock: user_content.dock.unwrap(),
109            default_width: px(user_content.default_width.unwrap()),
110            default_height: px(user_content.default_height.unwrap()),
111            detect_venv: project_content.detect_venv.unwrap(),
112            scroll_multiplier: user_content.scroll_multiplier.unwrap(),
113            max_scroll_history_lines: user_content.max_scroll_history_lines,
114            toolbar: Toolbar {
115                breadcrumbs: user_content.toolbar.unwrap().breadcrumbs.unwrap(),
116            },
117            scrollbar: ScrollbarSettings {
118                show: user_content.scrollbar.unwrap().show,
119            },
120            minimum_contrast: user_content.minimum_contrast.unwrap(),
121            path_hyperlink_regexes: project_content
122                .path_hyperlink_regexes
123                .unwrap()
124                .into_iter()
125                .map(|regex| match regex {
126                    PathHyperlinkRegex::SingleLine(regex) => regex,
127                    PathHyperlinkRegex::MultiLine(regex) => regex.join("\n"),
128                })
129                .collect(),
130            path_hyperlink_timeout_ms: project_content.path_hyperlink_timeout_ms.unwrap(),
131        }
132    }
133}
134
135#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
136#[serde(rename_all = "snake_case")]
137pub enum CursorShape {
138    /// Cursor is a block like `█`.
139    #[default]
140    Block,
141    /// Cursor is an underscore like `_`.
142    Underline,
143    /// Cursor is a vertical bar like `⎸`.
144    Bar,
145    /// Cursor is a hollow box like `▯`.
146    Hollow,
147}
148
149impl From<settings::CursorShapeContent> for CursorShape {
150    fn from(value: settings::CursorShapeContent) -> Self {
151        match value {
152            settings::CursorShapeContent::Block => CursorShape::Block,
153            settings::CursorShapeContent::Underline => CursorShape::Underline,
154            settings::CursorShapeContent::Bar => CursorShape::Bar,
155            settings::CursorShapeContent::Hollow => CursorShape::Hollow,
156        }
157    }
158}
159
160impl From<CursorShape> for AlacCursorShape {
161    fn from(value: CursorShape) -> Self {
162        match value {
163            CursorShape::Block => AlacCursorShape::Block,
164            CursorShape::Underline => AlacCursorShape::Underline,
165            CursorShape::Bar => AlacCursorShape::Beam,
166            CursorShape::Hollow => AlacCursorShape::HollowBlock,
167        }
168    }
169}
170
171impl From<CursorShape> for AlacCursorStyle {
172    fn from(value: CursorShape) -> Self {
173        AlacCursorStyle {
174            shape: value.into(),
175            blinking: false,
176        }
177    }
178}