terminal.rs

  1use std::path::PathBuf;
  2
  3use collections::HashMap;
  4use gpui::{AbsoluteLength, FontFeatures, SharedString, px};
  5use schemars::JsonSchema;
  6use serde::{Deserialize, Serialize};
  7use serde_with::skip_serializing_none;
  8use settings_macros::MergeFrom;
  9
 10use crate::FontFamilyName;
 11
 12#[skip_serializing_none]
 13#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
 14pub struct TerminalSettingsContent {
 15    /// What shell to use when opening a terminal.
 16    ///
 17    /// Default: system
 18    pub shell: Option<Shell>,
 19    /// What working directory to use when launching the terminal
 20    ///
 21    /// Default: current_project_directory
 22    pub working_directory: Option<WorkingDirectory>,
 23    /// Sets the terminal's font size.
 24    ///
 25    /// If this option is not included,
 26    /// the terminal will default to matching the buffer's font size.
 27    pub font_size: Option<f32>,
 28    /// Sets the terminal's font family.
 29    ///
 30    /// If this option is not included,
 31    /// the terminal will default to matching the buffer's font family.
 32    pub font_family: Option<FontFamilyName>,
 33
 34    /// Sets the terminal's font fallbacks.
 35    ///
 36    /// If this option is not included,
 37    /// the terminal will default to matching the buffer's font fallbacks.
 38    #[schemars(extend("uniqueItems" = true))]
 39    pub font_fallbacks: Option<Vec<FontFamilyName>>,
 40
 41    /// Sets the terminal's line height.
 42    ///
 43    /// Default: comfortable
 44    pub line_height: Option<TerminalLineHeight>,
 45    pub font_features: Option<FontFeatures>,
 46    /// Sets the terminal's font weight in CSS weight units 0-900.
 47    pub font_weight: Option<f32>,
 48    /// Any key-value pairs added to this list will be added to the terminal's
 49    /// environment. Use `:` to separate multiple values.
 50    ///
 51    /// Default: {}
 52    pub env: Option<HashMap<String, String>>,
 53    /// Default cursor shape for the terminal.
 54    /// Can be "bar", "block", "underline", or "hollow".
 55    ///
 56    /// Default: None
 57    pub cursor_shape: Option<CursorShapeContent>,
 58    /// Sets the cursor blinking behavior in the terminal.
 59    ///
 60    /// Default: terminal_controlled
 61    pub blinking: Option<TerminalBlink>,
 62    /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
 63    /// Alternate Scroll mode converts mouse scroll events into up / down key
 64    /// presses when in the alternate screen (e.g. when running applications
 65    /// like vim or  less). The terminal can still set and unset this mode.
 66    ///
 67    /// Default: on
 68    pub alternate_scroll: Option<AlternateScroll>,
 69    /// Sets whether the option key behaves as the meta key.
 70    ///
 71    /// Default: false
 72    pub option_as_meta: Option<bool>,
 73    /// Whether or not selecting text in the terminal will automatically
 74    /// copy to the system clipboard.
 75    ///
 76    /// Default: false
 77    pub copy_on_select: Option<bool>,
 78    /// Whether to keep the text selection after copying it to the clipboard.
 79    ///
 80    /// Default: false
 81    pub keep_selection_on_copy: Option<bool>,
 82    /// Whether to show the terminal button in the status bar.
 83    ///
 84    /// Default: true
 85    pub button: Option<bool>,
 86    pub dock: Option<TerminalDockPosition>,
 87    /// Default width when the terminal is docked to the left or right.
 88    ///
 89    /// Default: 640
 90    pub default_width: Option<f32>,
 91    /// Default height when the terminal is docked to the bottom.
 92    ///
 93    /// Default: 320
 94    pub default_height: Option<f32>,
 95    /// Activates the python virtual environment, if one is found, in the
 96    /// terminal's working directory (as resolved by the working_directory
 97    /// setting). Set this to "off" to disable this behavior.
 98    ///
 99    /// Default: on
100    pub detect_venv: Option<VenvSettings>,
101    /// The maximum number of lines to keep in the scrollback history.
102    /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
103    /// 0 disables the scrolling.
104    /// Existing terminals will not pick up this change until they are recreated.
105    /// 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.
106    ///
107    /// Default: 10_000
108    pub max_scroll_history_lines: Option<usize>,
109    /// Toolbar related settings
110    pub toolbar: Option<TerminalToolbarContent>,
111    /// Scrollbar-related settings
112    pub scrollbar: Option<ScrollbarSettingsContent>,
113    /// The minimum APCA perceptual contrast between foreground and background colors.
114    ///
115    /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
116    /// especially for dark mode. Values range from 0 to 106.
117    ///
118    /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
119    /// https://readtech.org/ARC/tests/bronze-simple-mode/
120    /// - 0: No contrast adjustment
121    /// - 45: Minimum for large fluent text (36px+)
122    /// - 60: Minimum for other content text
123    /// - 75: Minimum for body text
124    /// - 90: Preferred for body text
125    ///
126    /// Default: 45
127    pub minimum_contrast: Option<f32>,
128}
129
130/// Shell configuration to open the terminal with.
131#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
132#[serde(rename_all = "snake_case")]
133pub enum Shell {
134    /// Use the system's default terminal configuration in /etc/passwd
135    #[default]
136    System,
137    /// Use a specific program with no arguments.
138    Program(String),
139    /// Use a specific program with arguments.
140    WithArguments {
141        /// The program to run.
142        program: String,
143        /// The arguments to pass to the program.
144        args: Vec<String>,
145        /// An optional string to override the title of the terminal tab
146        title_override: Option<SharedString>,
147    },
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
151#[serde(rename_all = "snake_case")]
152pub enum WorkingDirectory {
153    /// Use the current file's project directory.  Will Fallback to the
154    /// first project directory strategy if unsuccessful.
155    CurrentProjectDirectory,
156    /// Use the first project in this workspace's directory.
157    FirstProjectDirectory,
158    /// Always use this platform's home directory (if it can be found).
159    AlwaysHome,
160    /// Always use a specific directory. This value will be shell expanded.
161    /// If this path is not a valid directory the terminal will default to
162    /// this platform's home directory  (if it can be found).
163    Always { directory: String },
164}
165
166#[skip_serializing_none]
167#[derive(
168    Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
169)]
170pub struct ScrollbarSettingsContent {
171    /// When to show the scrollbar in the terminal.
172    ///
173    /// Default: inherits editor scrollbar settings
174    pub show: Option<ShowScrollbar>,
175}
176
177#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)]
178#[serde(rename_all = "snake_case")]
179pub enum TerminalLineHeight {
180    /// Use a line height that's comfortable for reading, 1.618
181    #[default]
182    Comfortable,
183    /// Use a standard line height, 1.3. This option is useful for TUIs,
184    /// particularly if they use box characters
185    Standard,
186    /// Use a custom line height.
187    Custom(f32),
188}
189
190impl TerminalLineHeight {
191    pub fn value(&self) -> AbsoluteLength {
192        let value = match self {
193            TerminalLineHeight::Comfortable => 1.618,
194            TerminalLineHeight::Standard => 1.3,
195            TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
196        };
197        px(value).into()
198    }
199}
200
201/// When to show the scrollbar.
202///
203/// Default: auto
204#[derive(
205    Copy,
206    Clone,
207    Debug,
208    Default,
209    Serialize,
210    Deserialize,
211    JsonSchema,
212    MergeFrom,
213    PartialEq,
214    Eq,
215    strum::VariantArray,
216    strum::VariantNames,
217)]
218#[serde(rename_all = "snake_case")]
219pub enum ShowScrollbar {
220    /// Show the scrollbar if there's important information or
221    /// follow the system's configured behavior.
222    #[default]
223    Auto,
224    /// Match the system's configured behavior.
225    System,
226    /// Always show the scrollbar.
227    Always,
228    /// Never show the scrollbar.
229    Never,
230}
231
232#[derive(
233    Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom,
234)]
235#[serde(rename_all = "snake_case")]
236// todo() -> combine with CursorShape
237pub enum CursorShapeContent {
238    /// Cursor is a block like `█`.
239    #[default]
240    Block,
241    /// Cursor is an underscore like `_`.
242    Underline,
243    /// Cursor is a vertical bar like `⎸`.
244    Bar,
245    /// Cursor is a hollow box like `▯`.
246    Hollow,
247}
248
249#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
250#[serde(rename_all = "snake_case")]
251pub enum TerminalBlink {
252    /// Never blink the cursor, ignoring the terminal mode.
253    Off,
254    /// Default the cursor blink to off, but allow the terminal to
255    /// set blinking.
256    TerminalControlled,
257    /// Always blink the cursor, ignoring the terminal mode.
258    On,
259}
260
261#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
262#[serde(rename_all = "snake_case")]
263pub enum AlternateScroll {
264    On,
265    Off,
266}
267
268// Toolbar related settings
269#[skip_serializing_none]
270#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
271pub struct TerminalToolbarContent {
272    /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
273    /// Only shown if the terminal title is not empty.
274    ///
275    /// The shell running in the terminal needs to be configured to emit the title.
276    /// Example: `echo -e "\e]2;New Title\007";`
277    ///
278    /// Default: true
279    pub breadcrumbs: Option<bool>,
280}
281
282#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
283#[serde(rename_all = "snake_case")]
284pub enum VenvSettings {
285    #[default]
286    Off,
287    On {
288        /// Default directories to search for virtual environments, relative
289        /// to the current working directory. We recommend overriding this
290        /// in your project's settings, rather than globally.
291        activate_script: Option<ActivateScript>,
292        venv_name: Option<String>,
293        directories: Option<Vec<PathBuf>>,
294    },
295}
296#[skip_serializing_none]
297pub struct VenvSettingsContent<'a> {
298    pub activate_script: ActivateScript,
299    pub venv_name: &'a str,
300    pub directories: &'a [PathBuf],
301}
302
303impl VenvSettings {
304    pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
305        match self {
306            VenvSettings::Off => None,
307            VenvSettings::On {
308                activate_script,
309                venv_name,
310                directories,
311            } => Some(VenvSettingsContent {
312                activate_script: activate_script.unwrap_or(ActivateScript::Default),
313                venv_name: venv_name.as_deref().unwrap_or(""),
314                directories: directories.as_deref().unwrap_or(&[]),
315            }),
316        }
317    }
318}
319
320#[derive(
321    Copy,
322    Clone,
323    Debug,
324    Serialize,
325    Deserialize,
326    JsonSchema,
327    MergeFrom,
328    PartialEq,
329    Eq,
330    strum::VariantArray,
331    strum::VariantNames,
332)]
333#[serde(rename_all = "snake_case")]
334pub enum TerminalDockPosition {
335    Left,
336    Bottom,
337    Right,
338}
339
340#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
341#[serde(rename_all = "snake_case")]
342pub enum ActivateScript {
343    #[default]
344    Default,
345    Csh,
346    Fish,
347    Nushell,
348    PowerShell,
349    Pyenv,
350}