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