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