Skip to repository content556 lines · 17.4 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T02:36:48.045Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
terminal.rs
1use std::path::PathBuf;
2
3use collections::HashMap;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use settings_macros::{MergeFrom, with_fallible_options};
7
8use crate::{FontFamilyName, FontFeaturesContent, FontSize, FontWeightContent};
9
10#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
11pub struct ProjectTerminalSettingsContent {
12 /// What shell to use when opening a terminal.
13 ///
14 /// Default: system
15 pub shell: Option<Shell>,
16 /// What working directory to use when launching the terminal
17 ///
18 /// Default: current_project_directory
19 pub working_directory: Option<WorkingDirectory>,
20 /// Any key-value pairs added to this list will be added to the terminal's
21 /// environment. Use `:` to separate multiple values.
22 ///
23 /// Default: {}
24 pub env: Option<HashMap<String, String>>,
25 /// Activates the python virtual environment, if one is found, in the
26 /// terminal's working directory (as resolved by the working_directory
27 /// setting). Set this to "off" to disable this behavior.
28 ///
29 /// Default: on
30 pub detect_venv: Option<VenvSettings>,
31 /// Regexes used to identify paths for hyperlink navigation.
32 ///
33 /// Default: [
34 /// // Python-style diagnostics
35 /// "File \"(?<path>[^\"]+)\", line (?<line>[0-9]+)",
36 /// // Common path syntax with optional line, column, description, trailing punctuation, or
37 /// // surrounding symbols or quotes
38 /// [
39 /// "(?x)",
40 /// "# optionally starts with 0-2 opening prefix symbols",
41 /// "[({\\[<]{0,2}",
42 /// "# which may be followed by an opening quote",
43 /// "(?<quote>[\"'`])?",
44 /// "# `path` is the shortest sequence of any non-space character",
45 /// "(?<link>(?<path>[^ ]+?",
46 /// " # which may end with a line and optionally a column,",
47 /// " (?<line_column>:+[0-9]+(:[0-9]+)?|:?\\([0-9]+([,:][0-9]+)?\\))?",
48 /// "))",
49 /// "# which must be followed by a matching quote",
50 /// "(?(<quote>)\\k<quote>)",
51 /// "# and optionally a single closing symbol",
52 /// "[)}\\]>]?",
53 /// "# if line/column matched, may be followed by a description",
54 /// "(?(<line_column>):[^ 0-9][^ ]*)?",
55 /// "# which may be followed by trailing punctuation",
56 /// "[.,:)}\\]>]*",
57 /// "# and always includes trailing whitespace or end of line",
58 /// "([ ]+|$)"
59 /// ]
60 /// ]
61 pub path_hyperlink_regexes: Option<Vec<PathHyperlinkRegex>>,
62 /// Timeout for hover and Cmd-click path hyperlink discovery in milliseconds.
63 ///
64 /// Default: 1
65 pub path_hyperlink_timeout_ms: Option<u64>,
66}
67
68#[with_fallible_options]
69#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
70pub struct TerminalSettingsContent {
71 #[serde(flatten)]
72 pub project: ProjectTerminalSettingsContent,
73 /// Sets the terminal's font size.
74 ///
75 /// If this option is not included,
76 /// the terminal will default to matching the buffer's font size.
77 pub font_size: Option<FontSize>,
78 /// Sets the terminal's font family.
79 ///
80 /// If this option is not included,
81 /// the terminal will default to matching the buffer's font family.
82 pub font_family: Option<FontFamilyName>,
83
84 /// Sets the terminal's font fallbacks.
85 ///
86 /// If this option is not included,
87 /// the terminal will default to matching the buffer's font fallbacks.
88 #[schemars(extend("uniqueItems" = true))]
89 pub font_fallbacks: Option<Vec<FontFamilyName>>,
90
91 /// Sets the terminal's line height.
92 ///
93 /// Default: comfortable
94 pub line_height: Option<TerminalLineHeight>,
95 pub font_features: Option<FontFeaturesContent>,
96 /// Sets the terminal's font weight in CSS weight units 0-900.
97 pub font_weight: Option<FontWeightContent>,
98 /// Default cursor shape for the terminal.
99 /// Can be "bar", "block", "underline", or "hollow".
100 ///
101 /// Default: "block"
102 pub cursor_shape: Option<CursorShapeContent>,
103 /// Sets the cursor blinking behavior in the terminal.
104 ///
105 /// Default: terminal_controlled
106 pub blinking: Option<TerminalBlink>,
107 /// Sets whether Alternate Scroll mode (code: ?1007) is active by default.
108 /// Alternate Scroll mode converts mouse scroll events into up / down key
109 /// presses when in the alternate screen (e.g. when running applications
110 /// like vim or less). The terminal can still set and unset this mode.
111 ///
112 /// Default: on
113 pub alternate_scroll: Option<AlternateScroll>,
114 /// Sets whether the option key behaves as the meta key.
115 ///
116 /// Default: false
117 pub option_as_meta: Option<bool>,
118 /// Whether or not selecting text in the terminal will automatically
119 /// copy to the system clipboard.
120 ///
121 /// Default: false
122 pub copy_on_select: Option<bool>,
123 /// Whether to keep the text selection after copying it to the clipboard.
124 ///
125 /// Default: true
126 pub keep_selection_on_copy: Option<bool>,
127 /// Whether cmd-click (ctrl-click on Linux and Windows) opens hyperlinks even
128 /// when the terminal application has enabled mouse reporting (e.g. vim with
129 /// mouse=a, htop). When false, these clicks are forwarded to the application
130 /// instead, and hyperlinks can still be opened with shift-cmd-click
131 /// (shift-ctrl-click).
132 ///
133 /// Default: true
134 pub open_links_in_mouse_mode: Option<bool>,
135 /// Whether to show the terminal button in the status bar.
136 ///
137 /// Default: true
138 pub button: Option<bool>,
139 pub dock: Option<TerminalDockPosition>,
140 /// Whether the terminal panel should use flexible (proportional) sizing.
141 ///
142 /// Default: true
143 pub flexible: Option<bool>,
144 /// Default width when the terminal is docked to the left or right.
145 ///
146 /// Default: 640
147 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
148 pub default_width: Option<f32>,
149 /// Default height when the terminal is docked to the bottom.
150 ///
151 /// Default: 320
152 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
153 pub default_height: Option<f32>,
154 /// The maximum number of lines to keep in the scrollback history.
155 /// Maximum allowed value is 100_000, all values above that will be treated as 100_000.
156 /// 0 disables the scrolling.
157 /// Existing terminals will not pick up this change until they are recreated.
158 /// 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.
159 ///
160 /// Default: 10_000
161 pub max_scroll_history_lines: Option<usize>,
162 /// The multiplier for scrolling with the mouse wheel.
163 ///
164 /// Default: 1.0
165 pub scroll_multiplier: Option<f32>,
166 /// Toolbar related settings
167 pub toolbar: Option<TerminalToolbarContent>,
168 /// Scrollbar-related settings
169 pub scrollbar: Option<ScrollbarSettingsContent>,
170 /// The minimum APCA perceptual contrast between foreground and background colors.
171 ///
172 /// APCA (Accessible Perceptual Contrast Algorithm) is more accurate than WCAG 2.x,
173 /// especially for dark mode. Values range from 0 to 106.
174 ///
175 /// Based on APCA Readability Criterion (ARC) Bronze Simple Mode:
176 /// https://readtech.org/ARC/tests/bronze-simple-mode/
177 /// - 0: No contrast adjustment
178 /// - 45: Minimum for large fluent text (36px+)
179 /// - 60: Minimum for other content text
180 /// - 75: Minimum for body text
181 /// - 90: Preferred for body text
182 ///
183 /// Default: 45
184 #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
185 pub minimum_contrast: Option<f32>,
186 /// Whether to show a badge on the terminal panel icon with the count of open terminals.
187 ///
188 /// Default: false
189 pub show_count_badge: Option<bool>,
190 /// What to do when the `BEL` character (`\a`) is printed to terminal.
191 ///
192 /// Default: "system"
193 pub bell: Option<TerminalBell>,
194}
195
196/// Shell configuration to open the terminal with.
197#[derive(
198 Clone,
199 Debug,
200 Default,
201 Serialize,
202 Deserialize,
203 PartialEq,
204 Eq,
205 JsonSchema,
206 MergeFrom,
207 strum::EnumDiscriminants,
208)]
209#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
210#[serde(rename_all = "snake_case")]
211pub enum Shell {
212 /// Use the system's default terminal configuration in /etc/passwd
213 #[default]
214 System,
215 /// Use a specific program with no arguments.
216 Program(String),
217 /// Use a specific program with arguments.
218 WithArguments {
219 /// The program to run.
220 program: String,
221 /// The arguments to pass to the program.
222 args: Vec<String>,
223 /// An optional string to override the title of the terminal tab
224 title_override: Option<String>,
225 },
226}
227
228#[derive(
229 Clone,
230 Debug,
231 Serialize,
232 Deserialize,
233 PartialEq,
234 Eq,
235 JsonSchema,
236 MergeFrom,
237 strum::EnumDiscriminants,
238)]
239#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))]
240#[serde(rename_all = "snake_case")]
241pub enum WorkingDirectory {
242 /// Use the current file's directory, falling back to the project directory,
243 /// then the first project in the workspace.
244 CurrentFileDirectory,
245 /// Use the current file's project directory. Fallback to the
246 /// first project directory strategy if unsuccessful.
247 CurrentProjectDirectory,
248 /// Use the first project in this workspace's directory. Fallback to using
249 /// this platform's home directory.
250 FirstProjectDirectory,
251 /// Always use this platform's home directory (if it can be found).
252 AlwaysHome,
253 /// Always use a specific directory. This value will be shell expanded.
254 /// If this path is not a valid directory the terminal will default to
255 /// this platform's home directory (if it can be found).
256 Always { directory: String },
257}
258
259#[with_fallible_options]
260#[derive(
261 Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
262)]
263pub struct ScrollbarSettingsContent {
264 /// When to show the scrollbar in the terminal.
265 ///
266 /// Default: inherits editor scrollbar settings
267 pub show: Option<ShowScrollbar>,
268}
269
270#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)]
271#[serde(rename_all = "snake_case")]
272pub enum TerminalLineHeight {
273 /// Use a line height that's comfortable for reading, 1.618
274 #[default]
275 Comfortable,
276 /// Use a standard line height, 1.3. This option is useful for TUIs,
277 /// particularly if they use box characters
278 Standard,
279 /// Use a custom line height.
280 Custom(#[serde(serialize_with = "crate::serialize_f32_with_two_decimal_places")] f32),
281}
282
283impl TerminalLineHeight {
284 pub fn value(&self) -> f32 {
285 match self {
286 TerminalLineHeight::Comfortable => 1.618,
287 TerminalLineHeight::Standard => 1.3,
288 TerminalLineHeight::Custom(line_height) => f32::max(*line_height, 1.),
289 }
290 }
291}
292
293/// When to show the scrollbar.
294///
295/// Default: auto
296#[derive(
297 Copy,
298 Clone,
299 Debug,
300 Default,
301 Serialize,
302 Deserialize,
303 JsonSchema,
304 MergeFrom,
305 PartialEq,
306 Eq,
307 strum::VariantArray,
308 strum::VariantNames,
309)]
310#[serde(rename_all = "snake_case")]
311pub enum ShowScrollbar {
312 /// Show the scrollbar if there's important information or
313 /// follow the system's configured behavior.
314 #[default]
315 Auto,
316 /// Match the system's configured behavior.
317 System,
318 /// Always show the scrollbar.
319 Always,
320 /// Never show the scrollbar.
321 Never,
322}
323
324#[derive(
325 Clone,
326 Copy,
327 Debug,
328 Default,
329 Serialize,
330 Deserialize,
331 PartialEq,
332 Eq,
333 JsonSchema,
334 MergeFrom,
335 strum::VariantArray,
336 strum::VariantNames,
337)]
338#[serde(rename_all = "snake_case")]
339// todo() -> combine with CursorShape
340pub enum CursorShapeContent {
341 /// Cursor is a block like `█`.
342 #[default]
343 Block,
344 /// Cursor is an underscore like `_`.
345 Underline,
346 /// Cursor is a vertical bar like `⎸`.
347 Bar,
348 /// Cursor is a hollow box like `▯`.
349 Hollow,
350}
351
352#[derive(
353 Copy,
354 Clone,
355 Debug,
356 Serialize,
357 Deserialize,
358 PartialEq,
359 Eq,
360 JsonSchema,
361 MergeFrom,
362 strum::VariantArray,
363 strum::VariantNames,
364)]
365#[serde(rename_all = "snake_case")]
366pub enum TerminalBlink {
367 /// Never blink the cursor, ignoring the terminal mode.
368 Off,
369 /// Default the cursor blink to off, but allow the terminal to
370 /// set blinking.
371 TerminalControlled,
372 /// Always blink the cursor, ignoring the terminal mode.
373 On,
374}
375
376#[derive(
377 Clone,
378 Copy,
379 Debug,
380 Serialize,
381 Deserialize,
382 PartialEq,
383 Eq,
384 JsonSchema,
385 MergeFrom,
386 strum::VariantArray,
387 strum::VariantNames,
388)]
389#[serde(rename_all = "snake_case")]
390pub enum AlternateScroll {
391 On,
392 Off,
393}
394
395// Toolbar related settings
396#[with_fallible_options]
397#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
398pub struct TerminalToolbarContent {
399 /// Whether to display the terminal title in breadcrumbs inside the terminal pane.
400 /// Only shown if the terminal title is not empty.
401 ///
402 /// The shell running in the terminal needs to be configured to emit the title.
403 /// Example: `echo -e "\e]2;New Title\007";`
404 ///
405 /// Default: true
406 pub breadcrumbs: Option<bool>,
407}
408
409#[derive(
410 Copy,
411 Clone,
412 Debug,
413 Default,
414 PartialEq,
415 Eq,
416 Serialize,
417 Deserialize,
418 JsonSchema,
419 MergeFrom,
420 strum::VariantArray,
421 strum::VariantNames,
422)]
423#[serde(rename_all = "snake_case")]
424pub enum TerminalBell {
425 /// Play an OS-specific alert sound.
426 #[default]
427 System,
428 /// Do not play any sound.
429 Off,
430}
431
432#[derive(
433 Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
434)]
435#[serde(rename_all = "snake_case")]
436pub enum CondaManager {
437 /// Automatically detect the conda manager
438 #[default]
439 Auto,
440 /// Use conda
441 Conda,
442 /// Use mamba
443 Mamba,
444 /// Use micromamba
445 Micromamba,
446}
447
448#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
449#[serde(rename_all = "snake_case")]
450pub enum VenvSettings {
451 #[default]
452 Off,
453 On {
454 /// Default directories to search for virtual environments, relative
455 /// to the current working directory. We recommend overriding this
456 /// in your project's settings, rather than globally.
457 activate_script: Option<ActivateScript>,
458 venv_name: Option<String>,
459 directories: Option<Vec<PathBuf>>,
460 /// Preferred Conda manager to use when activating Conda environments.
461 ///
462 /// Default: auto
463 conda_manager: Option<CondaManager>,
464 },
465}
466#[with_fallible_options]
467pub struct VenvSettingsContent<'a> {
468 pub activate_script: ActivateScript,
469 pub venv_name: &'a str,
470 pub directories: &'a [PathBuf],
471 pub conda_manager: CondaManager,
472}
473
474impl VenvSettings {
475 pub fn as_option(&self) -> Option<VenvSettingsContent<'_>> {
476 match self {
477 VenvSettings::Off => None,
478 VenvSettings::On {
479 activate_script,
480 venv_name,
481 directories,
482 conda_manager,
483 } => Some(VenvSettingsContent {
484 activate_script: activate_script.unwrap_or(ActivateScript::Default),
485 venv_name: venv_name.as_deref().unwrap_or(""),
486 directories: directories.as_deref().unwrap_or(&[]),
487 conda_manager: conda_manager.unwrap_or(CondaManager::Auto),
488 }),
489 }
490 }
491}
492
493#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
494#[serde(untagged)]
495pub enum PathHyperlinkRegex {
496 SingleLine(String),
497 MultiLine(Vec<String>),
498}
499
500#[derive(
501 Copy,
502 Clone,
503 Debug,
504 Serialize,
505 Deserialize,
506 JsonSchema,
507 MergeFrom,
508 PartialEq,
509 Eq,
510 strum::VariantArray,
511 strum::VariantNames,
512)]
513#[serde(rename_all = "snake_case")]
514pub enum TerminalDockPosition {
515 Left,
516 Bottom,
517 Right,
518}
519
520#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
521#[serde(rename_all = "snake_case")]
522pub enum ActivateScript {
523 #[default]
524 Default,
525 Csh,
526 Fish,
527 Nushell,
528 PowerShell,
529 Pyenv,
530}
531
532#[cfg(test)]
533mod test {
534 use serde_json::json;
535
536 use crate::{ProjectSettingsContent, Shell};
537
538 #[test]
539 #[ignore]
540 fn test_project_settings() {
541 let project_content =
542 json!({"terminal": {"shell": {"program": "/bin/project"}}, "option_as_meta": true});
543
544 let _user_content =
545 json!({"terminal": {"shell": {"program": "/bin/user"}}, "option_as_meta": false});
546
547 let project_settings =
548 serde_json::from_value::<ProjectSettingsContent>(project_content).unwrap();
549
550 assert_eq!(
551 project_settings.terminal.unwrap().shell,
552 Some(Shell::Program("/bin/project".to_owned()))
553 );
554 }
555}
556