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