terminal_settings.rs

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