1use alacritty_terminal::vte::ansi::{
2 CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
3};
4use collections::HashMap;
5use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
6use schemars::JsonSchema;
7use serde_derive::{Deserialize, Serialize};
8
9use settings::SettingsSources;
10use std::path::PathBuf;
11use task::Shell;
12use theme::FontFamilyName;
13
14#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
15#[serde(rename_all = "snake_case")]
16pub enum TerminalDockPosition {
17 Left,
18 Bottom,
19 Right,
20}
21
22#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
23pub struct Toolbar {
24 pub breadcrumbs: bool,
25}
26
27#[derive(Clone, Debug, Deserialize)]
28pub struct TerminalSettings {
29 pub shell: Shell,
30 pub working_directory: WorkingDirectory,
31 pub font_size: Option<Pixels>,
32 pub font_family: Option<FontFamilyName>,
33 pub font_fallbacks: Option<FontFallbacks>,
34 pub font_features: Option<FontFeatures>,
35 pub font_weight: Option<FontWeight>,
36 pub line_height: TerminalLineHeight,
37 pub env: HashMap<String, String>,
38 pub cursor_shape: Option<CursorShape>,
39 pub blinking: TerminalBlink,
40 pub alternate_scroll: AlternateScroll,
41 pub option_as_meta: bool,
42 pub copy_on_select: bool,
43 pub keep_selection_on_copy: bool,
44 pub button: bool,
45 pub dock: TerminalDockPosition,
46 pub default_width: Pixels,
47 pub default_height: Pixels,
48 pub detect_venv: VenvSettings,
49 pub max_scroll_history_lines: Option<usize>,
50 pub toolbar: Toolbar,
51 pub scrollbar: ScrollbarSettings,
52}
53
54#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
55pub struct ScrollbarSettings {
56 /// When to show the scrollbar in the terminal.
57 ///
58 /// Default: inherits editor scrollbar settings
59 pub show: Option<ShowScrollbar>,
60}
61
62#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
63pub struct ScrollbarSettingsContent {
64 /// When to show the scrollbar in the terminal.
65 ///
66 /// Default: inherits editor scrollbar settings
67 pub show: Option<Option<ShowScrollbar>>,
68}
69
70/// When to show the scrollbar in the terminal.
71///
72/// Default: auto
73#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
74#[serde(rename_all = "snake_case")]
75pub enum ShowScrollbar {
76 /// Show the scrollbar if there's important information or
77 /// follow the system's configured behavior.
78 Auto,
79 /// Match the system's configured behavior.
80 System,
81 /// Always show the scrollbar.
82 Always,
83 /// Never show the scrollbar.
84 Never,
85}
86
87#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
88#[serde(rename_all = "snake_case")]
89pub enum VenvSettings {
90 #[default]
91 Off,
92 On {
93 /// Default directories to search for virtual environments, relative
94 /// to the current working directory. We recommend overriding this
95 /// in your project's settings, rather than globally.
96 activate_script: Option<ActivateScript>,
97 directories: Option<Vec<PathBuf>>,
98 },
99}
100
101pub struct VenvSettingsContent<'a> {
102 pub activate_script: ActivateScript,
103 pub directories: &'a [PathBuf],
104}
105
106impl VenvSettings {
107 pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
108 match self {
109 VenvSettings::Off => None,
110 VenvSettings::On {
111 activate_script,
112 directories,
113 } => Some(VenvSettingsContent {
114 activate_script: activate_script.unwrap_or(ActivateScript::Default),
115 directories: directories.as_deref().unwrap_or(&[]),
116 }),
117 }
118 }
119}
120
121#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
122#[serde(rename_all = "snake_case")]
123pub enum ActivateScript {
124 #[default]
125 Default,
126 Csh,
127 Fish,
128 Nushell,
129 PowerShell,
130}
131
132#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
133pub struct TerminalSettingsContent {
134 /// What shell to use when opening a terminal.
135 ///
136 /// Default: system
137 pub shell: Option<Shell>,
138 /// What working directory to use when launching the terminal
139 ///
140 /// Default: current_project_directory
141 pub working_directory: Option<WorkingDirectory>,
142 /// Sets the terminal's font size.
143 ///
144 /// If this option is not included,
145 /// the terminal will default to matching the buffer's font size.
146 pub font_size: Option<f32>,
147 /// Sets the terminal's font family.
148 ///
149 /// If this option is not included,
150 /// the terminal will default to matching the buffer's font family.
151 pub font_family: Option<FontFamilyName>,
152
153 /// Sets the terminal's font fallbacks.
154 ///
155 /// If this option is not included,
156 /// the terminal will default to matching the buffer's font fallbacks.
157 #[schemars(extend("uniqueItems" = true))]
158 pub font_fallbacks: Option<Vec<FontFamilyName>>,
159
160 /// Sets the terminal's line height.
161 ///
162 /// Default: comfortable
163 pub line_height: Option<TerminalLineHeight>,
164 pub font_features: Option<FontFeatures>,
165 /// Sets the terminal's font weight in CSS weight units 0-900.
166 pub font_weight: Option<f32>,
167 /// Any key-value pairs added to this list will be added to the terminal's
168 /// environment. Use `:` to separate multiple values.
169 ///
170 /// Default: {}
171 pub env: Option<HashMap<String, String>>,
172 /// Default cursor shape for the terminal.
173 /// Can be "bar", "block", "underline", or "hollow".
174 ///
175 /// Default: None
176 pub cursor_shape: Option<CursorShape>,
177 /// Sets the cursor blinking behavior in the terminal.
178 ///
179 /// Default: terminal_controlled
180 pub blinking: Option<TerminalBlink>,
181 /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
182 /// Alternate Scroll mode converts mouse scroll events into up / down key
183 /// presses when in the alternate screen (e.g. when running applications
184 /// like vim or less). The terminal can still set and unset this mode.
185 ///
186 /// Default: on
187 pub alternate_scroll: Option<AlternateScroll>,
188 /// Sets whether the option key behaves as the meta key.
189 ///
190 /// Default: false
191 pub option_as_meta: Option<bool>,
192 /// Whether or not selecting text in the terminal will automatically
193 /// copy to the system clipboard.
194 ///
195 /// Default: false
196 pub copy_on_select: Option<bool>,
197 /// Whether to keep the text selection after copying it to the clipboard.
198 ///
199 /// Default: false
200 pub keep_selection_on_copy: Option<bool>,
201 /// Whether to show the terminal button in the status bar.
202 ///
203 /// Default: true
204 pub button: Option<bool>,
205 pub dock: Option<TerminalDockPosition>,
206 /// Default width when the terminal is docked to the left or right.
207 ///
208 /// Default: 640
209 pub default_width: Option<f32>,
210 /// Default height when the terminal is docked to the bottom.
211 ///
212 /// Default: 320
213 pub default_height: Option<f32>,
214 /// Activates the python virtual environment, if one is found, in the
215 /// terminal's working directory (as resolved by the working_directory
216 /// setting). Set this to "off" to disable this behavior.
217 ///
218 /// Default: on
219 pub detect_venv: Option<VenvSettings>,
220 /// The maximum number of lines to keep in the scrollback history.
221 /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
222 /// 0 disables the scrolling.
223 /// Existing terminals will not pick up this change until they are recreated.
224 /// 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.
225 ///
226 /// Default: 10_000
227 pub max_scroll_history_lines: Option<usize>,
228 /// Toolbar related settings
229 pub toolbar: Option<ToolbarContent>,
230 /// Scrollbar-related settings
231 pub scrollbar: Option<ScrollbarSettingsContent>,
232}
233
234impl settings::Settings for TerminalSettings {
235 const KEY: Option<&'static str> = Some("terminal");
236
237 type FileContent = TerminalSettingsContent;
238
239 fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
240 sources.json_merge()
241 }
242
243 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
244 let name = |s| format!("terminal.integrated.{s}");
245
246 vscode.f32_setting(&name("fontSize"), &mut current.font_size);
247 if let Some(font_family) = vscode.read_string(&name("fontFamily")) {
248 current.font_family = Some(FontFamilyName(font_family.into()));
249 }
250 vscode.bool_setting(&name("copyOnSelection"), &mut current.copy_on_select);
251 vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta);
252 vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines);
253 match vscode.read_bool(&name("cursorBlinking")) {
254 Some(true) => current.blinking = Some(TerminalBlink::On),
255 Some(false) => current.blinking = Some(TerminalBlink::Off),
256 None => {}
257 }
258 vscode.enum_setting(
259 &name("cursorStyle"),
260 &mut current.cursor_shape,
261 |s| match s {
262 "block" => Some(CursorShape::Block),
263 "line" => Some(CursorShape::Bar),
264 "underline" => Some(CursorShape::Underline),
265 _ => None,
266 },
267 );
268 // they also have "none" and "outline" as options but just for the "Inactive" variant
269 if let Some(height) = vscode
270 .read_value(&name("lineHeight"))
271 .and_then(|v| v.as_f64())
272 {
273 current.line_height = Some(TerminalLineHeight::Custom(height as f32))
274 }
275
276 #[cfg(target_os = "windows")]
277 let platform = "windows";
278 #[cfg(target_os = "linux")]
279 let platform = "linux";
280 #[cfg(target_os = "macos")]
281 let platform = "osx";
282 #[cfg(target_os = "freebsd")]
283 let platform = "freebsd";
284
285 // TODO: handle arguments
286 let shell_name = format!("{platform}Exec");
287 if let Some(s) = vscode.read_string(&name(&shell_name)) {
288 current.shell = Some(Shell::Program(s.to_owned()))
289 }
290
291 if let Some(env) = vscode
292 .read_value(&name(&format!("env.{platform}")))
293 .and_then(|v| v.as_object())
294 {
295 for (k, v) in env {
296 if v.is_null() {
297 if let Some(zed_env) = current.env.as_mut() {
298 zed_env.remove(k);
299 }
300 }
301 let Some(v) = v.as_str() else { continue };
302 if let Some(zed_env) = current.env.as_mut() {
303 zed_env.insert(k.clone(), v.to_owned());
304 } else {
305 current.env = Some([(k.clone(), v.to_owned())].into_iter().collect())
306 }
307 }
308 }
309 }
310}
311
312#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
313#[serde(rename_all = "snake_case")]
314pub enum TerminalLineHeight {
315 /// Use a line height that's comfortable for reading, 1.618
316 #[default]
317 Comfortable,
318 /// Use a standard line height, 1.3. This option is useful for TUIs,
319 /// particularly if they use box characters
320 Standard,
321 /// Use a custom line height.
322 Custom(f32),
323}
324
325impl TerminalLineHeight {
326 pub fn value(&self) -> AbsoluteLength {
327 let value = match self {
328 TerminalLineHeight::Comfortable => 1.618,
329 TerminalLineHeight::Standard => 1.3,
330 TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
331 };
332 px(value).into()
333 }
334}
335
336#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
337#[serde(rename_all = "snake_case")]
338pub enum TerminalBlink {
339 /// Never blink the cursor, ignoring the terminal mode.
340 Off,
341 /// Default the cursor blink to off, but allow the terminal to
342 /// set blinking.
343 TerminalControlled,
344 /// Always blink the cursor, ignoring the terminal mode.
345 On,
346}
347
348#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
349#[serde(rename_all = "snake_case")]
350pub enum AlternateScroll {
351 On,
352 Off,
353}
354
355#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
356#[serde(rename_all = "snake_case")]
357pub enum WorkingDirectory {
358 /// Use the current file's project directory. Will Fallback to the
359 /// first project directory strategy if unsuccessful.
360 CurrentProjectDirectory,
361 /// Use the first project in this workspace's directory.
362 FirstProjectDirectory,
363 /// Always use this platform's home directory (if it can be found).
364 AlwaysHome,
365 /// Always use a specific directory. This value will be shell expanded.
366 /// If this path is not a valid directory the terminal will default to
367 /// this platform's home directory (if it can be found).
368 Always { directory: String },
369}
370
371// Toolbar related settings
372#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
373pub struct ToolbarContent {
374 /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
375 /// Only shown if the terminal title is not empty.
376 ///
377 /// The shell running in the terminal needs to be configured to emit the title.
378 /// Example: `echo -e "\e]2;New Title\007";`
379 ///
380 /// Default: true
381 pub breadcrumbs: Option<bool>,
382}
383
384#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
385#[serde(rename_all = "snake_case")]
386pub enum CursorShape {
387 /// Cursor is a block like `█`.
388 #[default]
389 Block,
390 /// Cursor is an underscore like `_`.
391 Underline,
392 /// Cursor is a vertical bar like `⎸`.
393 Bar,
394 /// Cursor is a hollow box like `▯`.
395 Hollow,
396}
397
398impl From<CursorShape> for AlacCursorShape {
399 fn from(value: CursorShape) -> Self {
400 match value {
401 CursorShape::Block => AlacCursorShape::Block,
402 CursorShape::Underline => AlacCursorShape::Underline,
403 CursorShape::Bar => AlacCursorShape::Beam,
404 CursorShape::Hollow => AlacCursorShape::HollowBlock,
405 }
406 }
407}
408
409impl From<CursorShape> for AlacCursorStyle {
410 fn from(value: CursorShape) -> Self {
411 AlacCursorStyle {
412 shape: value.into(),
413 blinking: false,
414 }
415 }
416}