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