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