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