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