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