Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:39:26.404Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

terminal.rs

556 lines · 17.4 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.