1use collections::HashMap;
2use gpui::{px, AbsoluteLength, AppContext, FontFeatures, FontWeight, Pixels};
3use schemars::{
4 gen::SchemaGenerator,
5 schema::{InstanceType, RootSchema, Schema, SchemaObject},
6 JsonSchema,
7};
8use serde_derive::{Deserialize, Serialize};
9use serde_json::Value;
10use settings::{SettingsJsonSchemaParams, SettingsSources};
11use std::path::PathBuf;
12
13#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum TerminalDockPosition {
16 Left,
17 Bottom,
18 Right,
19}
20
21#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
22pub struct Toolbar {
23 pub title: bool,
24}
25
26#[derive(Deserialize)]
27pub struct TerminalSettings {
28 pub shell: Shell,
29 pub working_directory: WorkingDirectory,
30 pub font_size: Option<Pixels>,
31 pub font_family: Option<String>,
32 pub line_height: TerminalLineHeight,
33 pub font_features: Option<FontFeatures>,
34 pub font_weight: Option<FontWeight>,
35 pub env: HashMap<String, String>,
36 pub blinking: TerminalBlink,
37 pub alternate_scroll: AlternateScroll,
38 pub option_as_meta: bool,
39 pub copy_on_select: bool,
40 pub button: bool,
41 pub dock: TerminalDockPosition,
42 pub default_width: Pixels,
43 pub default_height: Pixels,
44 pub detect_venv: VenvSettings,
45 pub max_scroll_history_lines: Option<usize>,
46 pub toolbar: Toolbar,
47}
48
49#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
50#[serde(rename_all = "snake_case")]
51pub enum VenvSettings {
52 #[default]
53 Off,
54 On {
55 /// Default directories to search for virtual environments, relative
56 /// to the current working directory. We recommend overriding this
57 /// in your project's settings, rather than globally.
58 activate_script: Option<ActivateScript>,
59 directories: Option<Vec<PathBuf>>,
60 },
61}
62
63pub struct VenvSettingsContent<'a> {
64 pub activate_script: ActivateScript,
65 pub directories: &'a [PathBuf],
66}
67
68impl VenvSettings {
69 pub fn as_option(&self) -> Option<VenvSettingsContent> {
70 match self {
71 VenvSettings::Off => None,
72 VenvSettings::On {
73 activate_script,
74 directories,
75 } => Some(VenvSettingsContent {
76 activate_script: activate_script.unwrap_or(ActivateScript::Default),
77 directories: directories.as_deref().unwrap_or(&[]),
78 }),
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
84#[serde(rename_all = "snake_case")]
85pub enum ActivateScript {
86 #[default]
87 Default,
88 Csh,
89 Fish,
90 Nushell,
91}
92
93#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
94pub struct TerminalSettingsContent {
95 /// What shell to use when opening a terminal.
96 ///
97 /// Default: system
98 pub shell: Option<Shell>,
99 /// What working directory to use when launching the terminal
100 ///
101 /// Default: current_project_directory
102 pub working_directory: Option<WorkingDirectory>,
103 /// Sets the terminal's font size.
104 ///
105 /// If this option is not included,
106 /// the terminal will default to matching the buffer's font size.
107 pub font_size: Option<f32>,
108 /// Sets the terminal's font family.
109 ///
110 /// If this option is not included,
111 /// the terminal will default to matching the buffer's font family.
112 pub font_family: Option<String>,
113 /// Sets the terminal's line height.
114 ///
115 /// Default: comfortable
116 pub line_height: Option<TerminalLineHeight>,
117 pub font_features: Option<FontFeatures>,
118 /// Sets the terminal's font weight in CSS weight units 0-900.
119 pub font_weight: Option<f32>,
120 /// Any key-value pairs added to this list will be added to the terminal's
121 /// environment. Use `:` to separate multiple values.
122 ///
123 /// Default: {}
124 pub env: Option<HashMap<String, String>>,
125 /// Sets the cursor blinking behavior in the terminal.
126 ///
127 /// Default: terminal_controlled
128 pub blinking: Option<TerminalBlink>,
129 /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
130 /// Alternate Scroll mode converts mouse scroll events into up / down key
131 /// presses when in the alternate screen (e.g. when running applications
132 /// like vim or less). The terminal can still set and unset this mode.
133 ///
134 /// Default: off
135 pub alternate_scroll: Option<AlternateScroll>,
136 /// Sets whether the option key behaves as the meta key.
137 ///
138 /// Default: false
139 pub option_as_meta: Option<bool>,
140 /// Whether or not selecting text in the terminal will automatically
141 /// copy to the system clipboard.
142 ///
143 /// Default: false
144 pub copy_on_select: Option<bool>,
145 /// Whether to show the terminal button in the status bar.
146 ///
147 /// Default: true
148 pub button: Option<bool>,
149 pub dock: Option<TerminalDockPosition>,
150 /// Default width when the terminal is docked to the left or right.
151 ///
152 /// Default: 640
153 pub default_width: Option<f32>,
154 /// Default height when the terminal is docked to the bottom.
155 ///
156 /// Default: 320
157 pub default_height: Option<f32>,
158 /// Activates the python virtual environment, if one is found, in the
159 /// terminal's working directory (as resolved by the working_directory
160 /// setting). Set this to "off" to disable this behavior.
161 ///
162 /// Default: on
163 pub detect_venv: Option<VenvSettings>,
164 /// The maximum number of lines to keep in the scrollback history.
165 /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
166 /// 0 disables the scrolling.
167 /// Existing terminals will not pick up this change until they are recreated.
168 /// 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.
169 ///
170 /// Default: 10_000
171 pub max_scroll_history_lines: Option<usize>,
172 /// Toolbar related settings
173 pub toolbar: Option<ToolbarContent>,
174}
175
176impl settings::Settings for TerminalSettings {
177 const KEY: Option<&'static str> = Some("terminal");
178
179 type FileContent = TerminalSettingsContent;
180
181 fn load(
182 sources: SettingsSources<Self::FileContent>,
183 _: &mut AppContext,
184 ) -> anyhow::Result<Self> {
185 sources.json_merge()
186 }
187
188 fn json_schema(
189 generator: &mut SchemaGenerator,
190 params: &SettingsJsonSchemaParams,
191 _: &AppContext,
192 ) -> RootSchema {
193 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
194 let available_fonts = params
195 .font_names
196 .iter()
197 .cloned()
198 .map(Value::String)
199 .collect();
200 let fonts_schema = SchemaObject {
201 instance_type: Some(InstanceType::String.into()),
202 enum_values: Some(available_fonts),
203 ..Default::default()
204 };
205 root_schema
206 .definitions
207 .extend([("FontFamilies".into(), fonts_schema.into())]);
208 root_schema
209 .schema
210 .object
211 .as_mut()
212 .unwrap()
213 .properties
214 .extend([(
215 "font_family".to_owned(),
216 Schema::new_ref("#/definitions/FontFamilies".into()),
217 )]);
218
219 root_schema
220 }
221}
222
223#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
224#[serde(rename_all = "snake_case")]
225pub enum TerminalLineHeight {
226 /// Use a line height that's comfortable for reading, 1.618
227 #[default]
228 Comfortable,
229 /// Use a standard line height, 1.3. This option is useful for TUIs,
230 /// particularly if they use box characters
231 Standard,
232 /// Use a custom line height.
233 Custom(f32),
234}
235
236impl TerminalLineHeight {
237 pub fn value(&self) -> AbsoluteLength {
238 let value = match self {
239 TerminalLineHeight::Comfortable => 1.618,
240 TerminalLineHeight::Standard => 1.3,
241 TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
242 };
243 px(value).into()
244 }
245}
246
247#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
248#[serde(rename_all = "snake_case")]
249pub enum TerminalBlink {
250 /// Never blink the cursor, ignoring the terminal mode.
251 Off,
252 /// Default the cursor blink to off, but allow the terminal to
253 /// set blinking.
254 TerminalControlled,
255 /// Always blink the cursor, ignoring the terminal mode.
256 On,
257}
258
259#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
260#[serde(rename_all = "snake_case")]
261pub enum Shell {
262 /// Use the system's default terminal configuration in /etc/passwd
263 System,
264 Program(String),
265 WithArguments {
266 program: String,
267 args: Vec<String>,
268 },
269}
270
271#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
272#[serde(rename_all = "snake_case")]
273pub enum AlternateScroll {
274 On,
275 Off,
276}
277
278#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
279#[serde(rename_all = "snake_case")]
280pub enum WorkingDirectory {
281 /// Use the current file's project directory. Will Fallback to the
282 /// first project directory strategy if unsuccessful.
283 CurrentProjectDirectory,
284 /// Use the first project in this workspace's directory.
285 FirstProjectDirectory,
286 /// Always use this platform's home directory (if it can be found).
287 AlwaysHome,
288 /// Always use a specific directory. This value will be shell expanded.
289 /// If this path is not a valid directory the terminal will default to
290 /// this platform's home directory (if it can be found).
291 Always { directory: String },
292}
293
294// Toolbar related settings
295#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
296pub struct ToolbarContent {
297 /// Whether to display the terminal title in its toolbar.
298 ///
299 /// Default: true
300 pub title: Option<bool>,
301}