terminal_settings.rs

  1use collections::HashMap;
  2use gpui::{px, AbsoluteLength, AppContext, FontFeatures, Pixels};
  3use schemars::{
  4    gen::SchemaGenerator,
  5    schema::{InstanceType, RootSchema, Schema, SchemaObject},
  6    JsonSchema,
  7};
  8use serde_derive::{Deserialize, Serialize};
  9use serde_json::Value;
 10use settings::{SettingsJsonSchemaParams, SettingsSources};
 11use std::path::PathBuf;
 12
 13#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 14#[serde(rename_all = "snake_case")]
 15pub enum TerminalDockPosition {
 16    Left,
 17    Bottom,
 18    Right,
 19}
 20
 21#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 22pub struct Toolbar {
 23    pub title: bool,
 24}
 25
 26#[derive(Deserialize)]
 27pub struct TerminalSettings {
 28    pub shell: Shell,
 29    pub working_directory: WorkingDirectory,
 30    pub font_size: Option<Pixels>,
 31    pub font_family: Option<String>,
 32    pub line_height: TerminalLineHeight,
 33    pub font_features: Option<FontFeatures>,
 34    pub env: HashMap<String, String>,
 35    pub blinking: TerminalBlink,
 36    pub alternate_scroll: AlternateScroll,
 37    pub option_as_meta: bool,
 38    pub copy_on_select: 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}
 47
 48#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 49#[serde(rename_all = "snake_case")]
 50pub enum VenvSettings {
 51    #[default]
 52    Off,
 53    On {
 54        /// Default directories to search for virtual environments, relative
 55        /// to the current working directory. We recommend overriding this
 56        /// in your project's settings, rather than globally.
 57        activate_script: Option<ActivateScript>,
 58        directories: Option<Vec<PathBuf>>,
 59    },
 60}
 61
 62pub struct VenvSettingsContent<'a> {
 63    pub activate_script: ActivateScript,
 64    pub directories: &'a [PathBuf],
 65}
 66
 67impl VenvSettings {
 68    pub fn as_option(&self) -> Option<VenvSettingsContent> {
 69        match self {
 70            VenvSettings::Off => None,
 71            VenvSettings::On {
 72                activate_script,
 73                directories,
 74            } => Some(VenvSettingsContent {
 75                activate_script: activate_script.unwrap_or(ActivateScript::Default),
 76                directories: directories.as_deref().unwrap_or(&[]),
 77            }),
 78        }
 79    }
 80}
 81
 82#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
 83#[serde(rename_all = "snake_case")]
 84pub enum ActivateScript {
 85    #[default]
 86    Default,
 87    Csh,
 88    Fish,
 89    Nushell,
 90}
 91
 92#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 93pub struct TerminalSettingsContent {
 94    /// What shell to use when opening a terminal.
 95    ///
 96    /// Default: system
 97    pub shell: Option<Shell>,
 98    /// What working directory to use when launching the terminal
 99    ///
100    /// Default: current_project_directory
101    pub working_directory: Option<WorkingDirectory>,
102    /// Sets the terminal's font size.
103    ///
104    /// If this option is not included,
105    /// the terminal will default to matching the buffer's font size.
106    pub font_size: Option<f32>,
107    /// Sets the terminal's font family.
108    ///
109    /// If this option is not included,
110    /// the terminal will default to matching the buffer's font family.
111    pub font_family: Option<String>,
112    /// Sets the terminal's line height.
113    ///
114    /// Default: comfortable
115    pub line_height: Option<TerminalLineHeight>,
116    pub font_features: Option<FontFeatures>,
117    /// Any key-value pairs added to this list will be added to the terminal's
118    /// environment. Use `:` to separate multiple values.
119    ///
120    /// Default: {}
121    pub env: Option<HashMap<String, String>>,
122    /// Sets the cursor blinking behavior in the terminal.
123    ///
124    /// Default: terminal_controlled
125    pub blinking: Option<TerminalBlink>,
126    /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
127    /// Alternate Scroll mode converts mouse scroll events into up / down key
128    /// presses when in the alternate screen (e.g. when running applications
129    /// like vim or  less). The terminal can still set and unset this mode.
130    ///
131    /// Default: off
132    pub alternate_scroll: Option<AlternateScroll>,
133    /// Sets whether the option key behaves as the meta key.
134    ///
135    /// Default: false
136    pub option_as_meta: Option<bool>,
137    /// Whether or not selecting text in the terminal will automatically
138    /// copy to the system clipboard.
139    ///
140    /// Default: false
141    pub copy_on_select: Option<bool>,
142    /// Whether to show the terminal button in the status bar.
143    ///
144    /// Default: true
145    pub button: Option<bool>,
146    pub dock: Option<TerminalDockPosition>,
147    /// Default width when the terminal is docked to the left or right.
148    ///
149    /// Default: 640
150    pub default_width: Option<f32>,
151    /// Default height when the terminal is docked to the bottom.
152    ///
153    /// Default: 320
154    pub default_height: Option<f32>,
155    /// Activates the python virtual environment, if one is found, in the
156    /// terminal's working directory (as resolved by the working_directory
157    /// setting). Set this to "off" to disable this behavior.
158    ///
159    /// Default: on
160    pub detect_venv: Option<VenvSettings>,
161    /// The maximum number of lines to keep in the scrollback history.
162    /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
163    /// 0 disables the scrolling.
164    /// Existing terminals will not pick up this change until they are recreated.
165    /// See <a href="https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213">Alacritty documentation</a> for more information.
166    ///
167    /// Default: 10_000
168    pub max_scroll_history_lines: Option<usize>,
169    /// Toolbar related settings
170    pub toolbar: Option<ToolbarContent>,
171}
172
173impl settings::Settings for TerminalSettings {
174    const KEY: Option<&'static str> = Some("terminal");
175
176    type FileContent = TerminalSettingsContent;
177
178    fn load(
179        sources: SettingsSources<Self::FileContent>,
180        _: &mut AppContext,
181    ) -> anyhow::Result<Self> {
182        sources.json_merge()
183    }
184
185    fn json_schema(
186        generator: &mut SchemaGenerator,
187        params: &SettingsJsonSchemaParams,
188        _: &AppContext,
189    ) -> RootSchema {
190        let mut root_schema = generator.root_schema_for::<Self::FileContent>();
191        let available_fonts = params
192            .font_names
193            .iter()
194            .cloned()
195            .map(Value::String)
196            .collect();
197        let fonts_schema = SchemaObject {
198            instance_type: Some(InstanceType::String.into()),
199            enum_values: Some(available_fonts),
200            ..Default::default()
201        };
202        root_schema
203            .definitions
204            .extend([("FontFamilies".into(), fonts_schema.into())]);
205        root_schema
206            .schema
207            .object
208            .as_mut()
209            .unwrap()
210            .properties
211            .extend([(
212                "font_family".to_owned(),
213                Schema::new_ref("#/definitions/FontFamilies".into()),
214            )]);
215
216        root_schema
217    }
218}
219
220#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
221#[serde(rename_all = "snake_case")]
222pub enum TerminalLineHeight {
223    /// Use a line height that's comfortable for reading, 1.618
224    #[default]
225    Comfortable,
226    /// Use a standard line height, 1.3. This option is useful for TUIs,
227    /// particularly if they use box characters
228    Standard,
229    /// Use a custom line height.
230    Custom(f32),
231}
232
233impl TerminalLineHeight {
234    pub fn value(&self) -> AbsoluteLength {
235        let value = match self {
236            TerminalLineHeight::Comfortable => 1.618,
237            TerminalLineHeight::Standard => 1.3,
238            TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
239        };
240        px(value).into()
241    }
242}
243
244#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
245#[serde(rename_all = "snake_case")]
246pub enum TerminalBlink {
247    /// Never blink the cursor, ignoring the terminal mode.
248    Off,
249    /// Default the cursor blink to off, but allow the terminal to
250    /// set blinking.
251    TerminalControlled,
252    /// Always blink the cursor, ignoring the terminal mode.
253    On,
254}
255
256#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
257#[serde(rename_all = "snake_case")]
258pub enum Shell {
259    /// Use the system's default terminal configuration in /etc/passwd
260    System,
261    Program(String),
262    WithArguments {
263        program: String,
264        args: Vec<String>,
265    },
266}
267
268#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
269#[serde(rename_all = "snake_case")]
270pub enum AlternateScroll {
271    On,
272    Off,
273}
274
275#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
276#[serde(rename_all = "snake_case")]
277pub enum WorkingDirectory {
278    /// Use the current file's project directory.  Will Fallback to the
279    /// first project directory strategy if unsuccessful.
280    CurrentProjectDirectory,
281    /// Use the first project in this workspace's directory.
282    FirstProjectDirectory,
283    /// Always use this platform's home directory (if it can be found).
284    AlwaysHome,
285    /// Always use a specific directory. This value will be shell expanded.
286    /// If this path is not a valid directory the terminal will default to
287    /// this platform's home directory  (if it can be found).
288    Always { directory: String },
289}
290
291// Toolbar related settings
292#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
293pub struct ToolbarContent {
294    /// Whether to display the terminal title in its toolbar.
295    ///
296    /// Default: true
297    pub title: Option<bool>,
298}