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