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