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