1use alacritty_terminal::vte::ansi::{
2 CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
3};
4use collections::HashMap;
5use gpui::{
6 px, AbsoluteLength, AppContext, FontFallbacks, FontFeatures, FontWeight, Pixels, SharedString,
7};
8use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
9use serde_derive::{Deserialize, Serialize};
10use settings::{add_references_to_properties, SettingsJsonSchemaParams, SettingsSources};
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}
51
52#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
53#[serde(rename_all = "snake_case")]
54pub enum VenvSettings {
55 #[default]
56 Off,
57 On {
58 /// Default directories to search for virtual environments, relative
59 /// to the current working directory. We recommend overriding this
60 /// in your project's settings, rather than globally.
61 activate_script: Option<ActivateScript>,
62 directories: Option<Vec<PathBuf>>,
63 },
64}
65
66pub struct VenvSettingsContent<'a> {
67 pub activate_script: ActivateScript,
68 pub directories: &'a [PathBuf],
69}
70
71impl VenvSettings {
72 pub fn as_option(&self) -> Option<VenvSettingsContent> {
73 match self {
74 VenvSettings::Off => None,
75 VenvSettings::On {
76 activate_script,
77 directories,
78 } => Some(VenvSettingsContent {
79 activate_script: activate_script.unwrap_or(ActivateScript::Default),
80 directories: directories.as_deref().unwrap_or(&[]),
81 }),
82 }
83 }
84}
85
86#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
87#[serde(rename_all = "snake_case")]
88pub enum ActivateScript {
89 #[default]
90 Default,
91 Csh,
92 Fish,
93 Nushell,
94 PowerShell,
95}
96
97#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
98pub struct TerminalSettingsContent {
99 /// What shell to use when opening a terminal.
100 ///
101 /// Default: system
102 pub shell: Option<Shell>,
103 /// What working directory to use when launching the terminal
104 ///
105 /// Default: current_project_directory
106 pub working_directory: Option<WorkingDirectory>,
107 /// Sets the terminal's font size.
108 ///
109 /// If this option is not included,
110 /// the terminal will default to matching the buffer's font size.
111 pub font_size: Option<f32>,
112 /// Sets the terminal's font family.
113 ///
114 /// If this option is not included,
115 /// the terminal will default to matching the buffer's font family.
116 pub font_family: Option<String>,
117
118 /// Sets the terminal's font fallbacks.
119 ///
120 /// If this option is not included,
121 /// the terminal will default to matching the buffer's font fallbacks.
122 pub font_fallbacks: Option<Vec<String>>,
123
124 /// Sets the terminal's line height.
125 ///
126 /// Default: comfortable
127 pub line_height: Option<TerminalLineHeight>,
128 pub font_features: Option<FontFeatures>,
129 /// Sets the terminal's font weight in CSS weight units 0-900.
130 pub font_weight: Option<f32>,
131 /// Any key-value pairs added to this list will be added to the terminal's
132 /// environment. Use `:` to separate multiple values.
133 ///
134 /// Default: {}
135 pub env: Option<HashMap<String, String>>,
136 /// Default cursor shape for the terminal.
137 /// Can be "bar", "block", "underline", or "hollow".
138 ///
139 /// Default: None
140 pub cursor_shape: Option<CursorShape>,
141 /// Sets the cursor blinking behavior in the terminal.
142 ///
143 /// Default: terminal_controlled
144 pub blinking: Option<TerminalBlink>,
145 /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
146 /// Alternate Scroll mode converts mouse scroll events into up / down key
147 /// presses when in the alternate screen (e.g. when running applications
148 /// like vim or less). The terminal can still set and unset this mode.
149 ///
150 /// Default: off
151 pub alternate_scroll: Option<AlternateScroll>,
152 /// Sets whether the option key behaves as the meta key.
153 ///
154 /// Default: false
155 pub option_as_meta: Option<bool>,
156 /// Whether or not selecting text in the terminal will automatically
157 /// copy to the system clipboard.
158 ///
159 /// Default: false
160 pub copy_on_select: Option<bool>,
161 /// Whether to show the terminal button in the status bar.
162 ///
163 /// Default: true
164 pub button: Option<bool>,
165 pub dock: Option<TerminalDockPosition>,
166 /// Default width when the terminal is docked to the left or right.
167 ///
168 /// Default: 640
169 pub default_width: Option<f32>,
170 /// Default height when the terminal is docked to the bottom.
171 ///
172 /// Default: 320
173 pub default_height: Option<f32>,
174 /// Activates the python virtual environment, if one is found, in the
175 /// terminal's working directory (as resolved by the working_directory
176 /// setting). Set this to "off" to disable this behavior.
177 ///
178 /// Default: on
179 pub detect_venv: Option<VenvSettings>,
180 /// The maximum number of lines to keep in the scrollback history.
181 /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
182 /// 0 disables the scrolling.
183 /// Existing terminals will not pick up this change until they are recreated.
184 /// 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.
185 ///
186 /// Default: 10_000
187 pub max_scroll_history_lines: Option<usize>,
188 /// Toolbar related settings
189 pub toolbar: Option<ToolbarContent>,
190}
191
192impl settings::Settings for TerminalSettings {
193 const KEY: Option<&'static str> = Some("terminal");
194
195 type FileContent = TerminalSettingsContent;
196
197 fn load(
198 sources: SettingsSources<Self::FileContent>,
199 _: &mut AppContext,
200 ) -> anyhow::Result<Self> {
201 sources.json_merge()
202 }
203
204 fn json_schema(
205 generator: &mut SchemaGenerator,
206 params: &SettingsJsonSchemaParams,
207 _: &AppContext,
208 ) -> RootSchema {
209 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
210 root_schema.definitions.extend([
211 ("FontFamilies".into(), params.font_family_schema()),
212 ("FontFallbacks".into(), params.font_fallback_schema()),
213 ]);
214
215 add_references_to_properties(
216 &mut root_schema,
217 &[
218 ("font_family", "#/definitions/FontFamilies"),
219 ("font_fallbacks", "#/definitions/FontFallbacks"),
220 ],
221 );
222
223 root_schema
224 }
225}
226
227#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
228#[serde(rename_all = "snake_case")]
229pub enum TerminalLineHeight {
230 /// Use a line height that's comfortable for reading, 1.618
231 #[default]
232 Comfortable,
233 /// Use a standard line height, 1.3. This option is useful for TUIs,
234 /// particularly if they use box characters
235 Standard,
236 /// Use a custom line height.
237 Custom(f32),
238}
239
240impl TerminalLineHeight {
241 pub fn value(&self) -> AbsoluteLength {
242 let value = match self {
243 TerminalLineHeight::Comfortable => 1.618,
244 TerminalLineHeight::Standard => 1.3,
245 TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
246 };
247 px(value).into()
248 }
249}
250
251#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
252#[serde(rename_all = "snake_case")]
253pub enum TerminalBlink {
254 /// Never blink the cursor, ignoring the terminal mode.
255 Off,
256 /// Default the cursor blink to off, but allow the terminal to
257 /// set blinking.
258 TerminalControlled,
259 /// Always blink the cursor, ignoring the terminal mode.
260 On,
261}
262
263#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
264#[serde(rename_all = "snake_case")]
265pub enum AlternateScroll {
266 On,
267 Off,
268}
269
270#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
271#[serde(rename_all = "snake_case")]
272pub enum WorkingDirectory {
273 /// Use the current file's project directory. Will Fallback to the
274 /// first project directory strategy if unsuccessful.
275 CurrentProjectDirectory,
276 /// Use the first project in this workspace's directory.
277 FirstProjectDirectory,
278 /// Always use this platform's home directory (if it can be found).
279 AlwaysHome,
280 /// Always use a specific directory. This value will be shell expanded.
281 /// If this path is not a valid directory the terminal will default to
282 /// this platform's home directory (if it can be found).
283 Always { directory: String },
284}
285
286// Toolbar related settings
287#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
288pub struct ToolbarContent {
289 /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
290 /// Only shown if the terminal title is not empty.
291 ///
292 /// The shell running in the terminal needs to be configured to emit the title.
293 /// Example: `echo -e "\e]2;New Title\007";`
294 ///
295 /// Default: true
296 pub breadcrumbs: Option<bool>,
297}
298
299#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
300#[serde(rename_all = "snake_case")]
301pub enum CursorShape {
302 /// Cursor is a block like `█`.
303 #[default]
304 Block,
305 /// Cursor is an underscore like `_`.
306 Underline,
307 /// Cursor is a vertical bar like `⎸`.
308 Bar,
309 /// Cursor is a hollow box like `▯`.
310 Hollow,
311}
312
313impl From<CursorShape> for AlacCursorShape {
314 fn from(value: CursorShape) -> Self {
315 match value {
316 CursorShape::Block => AlacCursorShape::Block,
317 CursorShape::Underline => AlacCursorShape::Underline,
318 CursorShape::Bar => AlacCursorShape::Beam,
319 CursorShape::Hollow => AlacCursorShape::HollowBlock,
320 }
321 }
322}
323
324impl From<CursorShape> for AlacCursorStyle {
325 fn from(value: CursorShape) -> Self {
326 AlacCursorStyle {
327 shape: value.into(),
328 blinking: false,
329 }
330 }
331}