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