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}