From 3100773ae0418c74b64f953544a1d64a8fa111fc Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 25 Apr 2025 17:18:49 +0200 Subject: [PATCH] toml-config: add named actions --- Cargo.lock | 7 ++ release-notes.md | 20 ++++ toml-config/Cargo.toml | 1 + toml-config/src/config.rs | 20 ++++ toml-config/src/config/parsers.rs | 1 + toml-config/src/config/parsers/action.rs | 30 +++++ toml-config/src/config/parsers/actions.rs | 78 ++++++++++++ toml-config/src/config/parsers/config.rs | 36 +++++- toml-config/src/lib.rs | 55 ++++++++- toml-spec/spec/spec.generated.json | 74 ++++++++++++ toml-spec/spec/spec.generated.md | 139 ++++++++++++++++++++++ toml-spec/spec/spec.yaml | 121 +++++++++++++++++++ toml-spec/src/json_schema.rs | 2 + toml-spec/src/markdown.rs | 6 + toml-spec/src/types.rs | 1 + 15 files changed, 587 insertions(+), 4 deletions(-) create mode 100644 toml-config/src/config/parsers/actions.rs diff --git a/Cargo.lock b/Cargo.lock index 1b7a9e56..59f37af4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -641,6 +641,7 @@ dependencies = [ "jay-config", "log", "phf", + "run-on-drop", "serde_json", "simplelog", "thiserror", @@ -1092,6 +1093,12 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "run-on-drop" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c9afdcca45aed5b357d64e0d033f6400c07a668aa4f9b92d25d39a884889457" + [[package]] name = "rustc-demangle" version = "0.1.24" diff --git a/release-notes.md b/release-notes.md index 4bcaf3ea..60dc13e8 100644 --- a/release-notes.md +++ b/release-notes.md @@ -7,6 +7,26 @@ - Implement tablet-v2 version 2. - Floating windows can now be pinned. A pinned floating window stays visible on its output even when switching workspaces. +- The toml config can now contain named actions: + + ```toml + actions.switch-to-1 = [ + { type = "show-workspace", name = "1" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-2" }, + ] + actions.switch-to-2 = [ + { type = "show-workspace", name = "2" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-3" }, + ] + actions.switch-to-3 = [ + { type = "show-workspace", name = "3" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-1" }, + ] + actions.switch-to-next = "$switch-to-1" + + [shortcuts] + alt-x = "$switch-to-next" + ``` # 1.10.0 (2025-04-22) diff --git a/toml-config/Cargo.toml b/toml-config/Cargo.toml index 551dfbf5..4e393335 100644 --- a/toml-config/Cargo.toml +++ b/toml-config/Cargo.toml @@ -18,6 +18,7 @@ phf = { version = "0.11.2", features = ["macros"] } indexmap = "2.2.5" bstr = { version = "1.9.1", default-features = false } ahash = "0.8.11" +run-on-drop = "1.0.0" [dev-dependencies] simplelog = { version = "0.12.2", features = ["test"] } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 9facb61a..eedd0076 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -33,6 +33,7 @@ use { std::{ error::Error, fmt::{Display, Formatter}, + rc::Rc, time::Duration, }, thiserror::Error, @@ -64,6 +65,7 @@ pub enum SimpleCommand { } #[derive(Debug, Clone)] +#[expect(clippy::enum_variant_names)] pub enum Action { ConfigureConnector { con: ConfigConnector, @@ -133,6 +135,16 @@ pub enum Action { SetRepeatRate { rate: RepeatRate, }, + DefineAction { + name: String, + action: Box, + }, + UndefineAction { + name: String, + }, + NamedAction { + name: String, + }, } #[derive(Debug, Clone, Default)] @@ -338,6 +350,12 @@ pub struct Shortcut { pub latch: Option, } +#[derive(Debug, Clone)] +pub struct NamedAction { + pub name: Rc, + pub action: Action, +} + #[derive(Debug, Clone)] pub struct Config { pub keymap: Option, @@ -371,6 +389,8 @@ pub struct Config { pub xwayland: Option, pub color_management: Option, pub float: Option, + pub named_actions: Vec, + pub max_action_depth: u64, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index b6450d25..0112410b 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -7,6 +7,7 @@ use { }; pub mod action; +mod actions; mod color; pub mod color_management; pub mod config; diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index 7131dd50..1bc66f55 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -87,6 +87,11 @@ pub struct ActionParser<'a>(pub &'a Context<'a>); impl ActionParser<'_> { fn parse_simple_cmd(&self, span: Span, string: &str) -> ParseResult { use {crate::config::SimpleCommand::*, jay_config::Direction::*}; + if let Some(name) = string.strip_prefix("$") { + return Ok(Action::NamedAction { + name: name.to_string(), + }); + } let cmd = match string { "focus-left" => Focus(Left), "focus-down" => Focus(Down), @@ -323,6 +328,28 @@ impl ActionParser<'_> { .map_spanned_err(ActionParserError::RepeatRate)?; Ok(Action::SetRepeatRate { rate }) } + + fn parse_undefine_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let (name,) = ext.extract((str("name"),))?; + Ok(Action::UndefineAction { + name: name.value.to_string(), + }) + } + + fn parse_define_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let (name, action) = ext.extract((str("name"), val("action")))?; + Ok(Action::DefineAction { + name: name.value.to_string(), + action: Box::new(action.parse(&mut ActionParser(self.0))?), + }) + } + + fn parse_named_action(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let (name,) = ext.extract((str("name"),))?; + Ok(Action::NamedAction { + name: name.value.to_string(), + }) + } } impl Parser for ActionParser<'_> { @@ -374,6 +401,9 @@ impl Parser for ActionParser<'_> { "configure-idle" => self.parse_configure_idle(&mut ext), "move-to-output" => self.parse_move_to_output(&mut ext), "set-repeat-rate" => self.parse_set_repeat_rate(&mut ext), + "define-action" => self.parse_define_action(&mut ext), + "undefine-action" => self.parse_undefine_action(&mut ext), + "named" => self.parse_named_action(&mut ext), v => { ext.ignore_unused(); return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); diff --git a/toml-config/src/config/parsers/actions.rs b/toml-config/src/config/parsers/actions.rs new file mode 100644 index 00000000..0743b5e7 --- /dev/null +++ b/toml-config/src/config/parsers/actions.rs @@ -0,0 +1,78 @@ +use { + crate::{ + config::{ + Action, NamedAction, + context::Context, + extractor::ExtractorError, + parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + parsers::action::ActionParser, + }, + toml::{ + toml_span::{Span, Spanned, SpannedExt}, + toml_value::Value, + }, + }, + indexmap::IndexMap, + std::{collections::HashSet, rc::Rc}, + thiserror::Error, +}; + +#[derive(Debug, Error)] +pub enum ActionsParserError { + #[error(transparent)] + Expected(#[from] UnexpectedDataType), + #[error(transparent)] + ExtractorError(#[from] ExtractorError), +} + +pub struct ActionsParser<'a, 'b> { + pub cx: &'a Context<'a>, + pub used_names: HashSet>>, + pub actions: &'b mut Vec, +} + +impl Parser for ActionsParser<'_, '_> { + type Value = (); + type Error = ActionsParserError; + const EXPECTED: &'static [DataType] = &[DataType::Table]; + + fn parse_table( + &mut self, + _span: Span, + table: &IndexMap, Spanned>, + ) -> ParseResult { + for (name, value) in table.iter() { + let Some(action) = parse_action(self.cx, &name.value, value) else { + continue; + }; + let name = Rc::new(name.value.clone()).spanned(name.span); + log_used(self.cx, &mut self.used_names, name.clone()); + self.actions.push(NamedAction { + name: name.value, + action, + }); + } + Ok(()) + } +} + +fn parse_action(cx: &Context<'_>, name: &str, value: &Spanned) -> Option { + match value.parse(&mut ActionParser(cx)) { + Ok(a) => Some(a), + Err(e) => { + log::warn!("Could not parse action for name {name}: {}", cx.error(e)); + None + } + } +} + +fn log_used(cx: &Context<'_>, used: &mut HashSet>>, key: Spanned>) { + if let Some(prev) = used.get(&key) { + log::warn!( + "Duplicate actions overrides previous definition: {}", + cx.error3(key.span) + ); + log::info!("Previous definition here: {}", cx.error3(prev.span)); + } + used.insert(key); +} diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index fa627f21..98520a14 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -3,10 +3,11 @@ use { config::{ Action, Config, Libei, Theme, UiDrag, context::Context, - extractor::{Extractor, ExtractorError, arr, bol, opt, recover, str, val}, + extractor::{Extractor, ExtractorError, arr, bol, int, opt, recover, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ action::ActionParser, + actions::ActionsParser, color_management::ColorManagementParser, connector::ConnectorsParser, drm_device::DrmDevicesParser, @@ -119,7 +120,7 @@ impl Parser for ConfigParser<'_> { ui_drag_val, xwayland_val, ), - (color_management_val, float_val), + (color_management_val, float_val, actions_val, max_action_depth_val), ) = ext.extract(( ( opt(val("keymap")), @@ -157,7 +158,12 @@ impl Parser for ConfigParser<'_> { opt(val("ui-drag")), opt(val("xwayland")), ), - (opt(val("color-management")), opt(val("float"))), + ( + opt(val("color-management")), + opt(val("float")), + opt(val("actions")), + recover(opt(int("max-action-depth"))), + ), ))?; let mut keymap = None; if let Some(value) = keymap_val { @@ -391,6 +397,28 @@ impl Parser for ConfigParser<'_> { } } } + let mut named_actions = vec![]; + if let Some(value) = actions_val { + let mut parser = ActionsParser { + cx: self.0, + used_names: Default::default(), + actions: &mut named_actions, + }; + if let Err(e) = value.parse(&mut parser) { + log::warn!("Could not parse named actions: {}", self.0.error(e)); + } + } + let mut max_action_depth = 16; + if let Some(mut value) = max_action_depth_val { + if value.value < 0 { + log::warn!( + "Max action depth should not be negative: {}", + self.0.error3(value.span) + ); + value.value = 0; + } + max_action_depth = value.value as _; + } Ok(Config { keymap, repeat_rate, @@ -423,6 +451,8 @@ impl Parser for ConfigParser<'_> { xwayland, color_management, float, + named_actions, + max_action_depth, }) } } diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 83f21d5f..7f164d4e 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -39,7 +39,14 @@ use { }, xwayland::set_x_scaling_mode, }, - std::{cell::RefCell, io::ErrorKind, path::PathBuf, rc::Rc, time::Duration}, + run_on_drop::on_drop, + std::{ + cell::{Cell, RefCell}, + io::ErrorKind, + path::PathBuf, + rc::Rc, + time::Duration, + }, }; fn default_seat() -> Seat { @@ -225,6 +232,40 @@ impl Action { Action::SetRepeatRate { rate } => { B::new(move || s.set_repeat_rate(rate.rate, rate.delay)) } + Action::DefineAction { name, action } => { + let state = state.clone(); + let action = action.into_rc_fn(&state); + let name = Rc::new(name); + B::new(move || { + state + .actions + .borrow_mut() + .insert(name.clone(), action.clone()); + }) + } + Action::UndefineAction { name } => { + let state = state.clone(); + B::new(move || { + state.actions.borrow_mut().remove(&name); + }) + } + Action::NamedAction { name } => { + let state = state.clone(); + B::new(move || { + let depth = state.action_depth.get(); + if depth >= state.action_depth_max { + log::error!("Maximum action depth reached"); + return; + } + state.action_depth.set(depth + 1); + let _reset = on_drop(|| state.action_depth.set(depth)); + let Some(action) = state.actions.borrow().get(&name).cloned() else { + log::error!("There is no action named {name}"); + return; + }; + action(); + }) + } } } } @@ -607,6 +648,7 @@ impl Output { } } +#[expect(clippy::type_complexity)] struct State { outputs: AHashMap, drm_devices: AHashMap, @@ -617,6 +659,10 @@ struct State { io_maps: Vec<(InputMatch, OutputMatch)>, io_inputs: RefCell>>, io_outputs: RefCell>>, + + action_depth_max: u64, + action_depth: Cell, + actions: RefCell, Rc>>, } impl Drop for State { @@ -914,8 +960,15 @@ fn load_config(initial_load: bool, persistent: &Rc) { io_maps, io_inputs: Default::default(), io_outputs: Default::default(), + action_depth_max: config.max_action_depth, + action_depth: Cell::new(0), + actions: Default::default(), }); state.set_status(&config.status); + for a in config.named_actions { + let action = a.action.into_rc_fn(&state); + state.actions.borrow_mut().insert(a.name, action); + } let mut switch_actions = vec![]; for input in &mut config.inputs { let mut actions = AHashMap::new(); diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 3a1b0a5c..23d55c17 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -18,6 +18,11 @@ "description": "The value should be the name of a `simple` action. See the description of that\nvariant for more details.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = \"quit\"\n ```\n", "$ref": "#/$defs/SimpleActionName" }, + { + "type": "string", + "description": "The value should be the name of a `named` action, prefixed with the `$` character.\n\nThis is the same as using the `named` action with the `$` removed.\n\n- Example:\n\n ```toml\n [actions]\n q = \"quit\"\n\n [shortcuts]\n alt-q = \"$q\"\n ```\n", + "pattern": "^\\$.*$" + }, { "type": "array", "description": "A list of actions to execute in sequence.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-q = [\n { type = \"exec\", exec = [\"notify-send\", \"exiting\"] },\n \"quit\",\n ]\n ```\n", @@ -46,6 +51,23 @@ "cmd" ] }, + { + "description": "A named action that was defined via the top-level `actions` table or a\n`define-action` action. These are usually written as plain strings with a `$`\nprefix.\n\n- Example 1:\n\n ```toml\n [actions]\n my-action = \"quit\"\n\n [shortcuts]\n alt-q = { type = \"named\", name = \"my-action\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-q = [\n { type = \"define-action\", name = \"my-action\", action = \"quit\" },\n { type = \"named\", name = \"my-action\" },\n ]\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "named" + }, + "name": { + "type": "string", + "description": "The named action to execute." + } + }, + "required": [ + "type", + "name" + ] + }, { "description": "A list of actions to execute in sequence. These are usually written as plain\narrays instead.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n alt-q = { type = \"multi\", actions = [\"quit\", \"quit\"] }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-q = [\"quit\", \"quit\"]\n ```\n", "type": "object", @@ -418,6 +440,45 @@ "type", "dev" ] + }, + { + "description": "Defines a name for an action. Usually you would define these by using the\ntop-level `actions` table. This action can be used to re-define actions.\n\n- Example:\n\n ```toml\n [actions]\n a1 = \"quit\"\n a2 = \"$a1\"\n\n [shortcuts]\n alt-q = [\n { type = \"define-action\", name = \"a2\", action = [] },\n \"$2\", # does nothing\n ]\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "define-action" + }, + "name": { + "type": "string", + "description": "The name of the action." + }, + "action": { + "description": "The action to execute.", + "$ref": "#/$defs/Action" + } + }, + "required": [ + "type", + "name", + "action" + ] + }, + { + "description": "Removes a named action.\n", + "type": "object", + "properties": { + "type": { + "const": "undefine-action" + }, + "name": { + "type": "string", + "description": "The name of the action." + } + }, + "required": [ + "type", + "name" + ] } ] } @@ -640,6 +701,19 @@ "float": { "description": "Configures the settings of floating windows.\n\n- Example:\n\n ```toml\n [float]\n show-pin-icon = true\n ```\n", "$ref": "#/$defs/Float" + }, + "actions": { + "description": "Named actions.\n\nNamed actions can be used everywhere an action can be used. This can be used to\navoid repeating the same action multiple times.\n\n- Example:\n\n ```toml\n actions.switch-to-1 = [\n { type = \"show-workspace\", name = \"1\" },\n { type = \"define-action\", name = \"switch-to-next\", action = \"$switch-to-2\" },\n ]\n actions.switch-to-2 = [\n { type = \"show-workspace\", name = \"2\" },\n { type = \"define-action\", name = \"switch-to-next\", action = \"$switch-to-3\" },\n ]\n actions.switch-to-3 = [\n { type = \"show-workspace\", name = \"3\" },\n { type = \"define-action\", name = \"switch-to-next\", action = \"$switch-to-1\" },\n ]\n actions.switch-to-next = \"$switch-to-1\"\n\n [shortcuts]\n alt-x = \"$switch-to-next\"\n ```\n", + "type": "object", + "additionalProperties": { + "description": "", + "$ref": "#/$defs/Action" + } + }, + "max-action-depth": { + "type": "integer", + "description": "The maximum call depth of named actions. This setting prevents infinite recursion\nwhen using named actions. Setting this value to 0 or less disables named actions\ncompletely. The default is `16`.\n", + "minimum": 0.0 } }, "required": [] diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index b31ee8da..c12423dd 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -57,6 +57,24 @@ variant for more details. The value should be a [SimpleActionName](#types-SimpleActionName). +#### A string + +The value should be the name of a `named` action, prefixed with the `$` character. + +This is the same as using the `named` action with the `$` removed. + +- Example: + + ```toml + [actions] + q = "quit" + + [shortcuts] + alt-q = "$q" + ``` + +The string should match the following regular expression: `^\$.*$` + #### An array A list of actions to execute in sequence. @@ -106,6 +124,40 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a [SimpleActionName](#types-SimpleActionName). +- `named`: + + A named action that was defined via the top-level `actions` table or a + `define-action` action. These are usually written as plain strings with a `$` + prefix. + + - Example 1: + + ```toml + [actions] + my-action = "quit" + + [shortcuts] + alt-q = { type = "named", name = "my-action" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = [ + { type = "define-action", name = "my-action", action = "quit" }, + { type = "named", name = "my-action" }, + ] + ``` + + The table has the following fields: + + - `name` (required): + + The named action to execute. + + The value of this field should be a string. + - `multi`: A list of actions to execute in sequence. These are usually written as plain @@ -574,6 +626,51 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a [DrmDeviceMatch](#types-DrmDeviceMatch). +- `define-action`: + + Defines a name for an action. Usually you would define these by using the + top-level `actions` table. This action can be used to re-define actions. + + - Example: + + ```toml + [actions] + a1 = "quit" + a2 = "$a1" + + [shortcuts] + alt-q = [ + { type = "define-action", name = "a2", action = [] }, + "$2", # does nothing + ] + ``` + + The table has the following fields: + + - `name` (required): + + The name of the action. + + The value of this field should be a string. + + - `action` (required): + + The action to execute. + + The value of this field should be a [Action](#types-Action). + +- `undefine-action`: + + Removes a named action. + + The table has the following fields: + + - `name` (required): + + The name of the action. + + The value of this field should be a string. + ### `Brightness` @@ -1278,6 +1375,48 @@ The table has the following fields: The value of this field should be a [Float](#types-Float). +- `actions` (optional): + + Named actions. + + Named actions can be used everywhere an action can be used. This can be used to + avoid repeating the same action multiple times. + + - Example: + + ```toml + actions.switch-to-1 = [ + { type = "show-workspace", name = "1" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-2" }, + ] + actions.switch-to-2 = [ + { type = "show-workspace", name = "2" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-3" }, + ] + actions.switch-to-3 = [ + { type = "show-workspace", name = "3" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-1" }, + ] + actions.switch-to-next = "$switch-to-1" + + [shortcuts] + alt-x = "$switch-to-next" + ``` + + The value of this field should be a table whose values are [Actions](#types-Action). + +- `max-action-depth` (optional): + + The maximum call depth of named actions. This setting prevents infinite recursion + when using named actions. Setting this value to 0 or less disables named actions + completely. The default is `16`. + + The value of this field should be a number. + + The numbers should be integers. + + The numbers should be greater than or equal to 0. + ### `Connector` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index d6b8ebf5..86b3c7e5 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -89,6 +89,22 @@ Action: [shortcuts] alt-q = "quit" ``` + - kind: string + pattern: "^\\$.*$" + description: | + The value should be the name of a `named` action, prefixed with the `$` character. + + This is the same as using the `named` action with the `$` removed. + + - Example: + + ```toml + [actions] + q = "quit" + + [shortcuts] + alt-q = "$q" + ``` - kind: array items: ref: Action @@ -130,6 +146,36 @@ Action: description: The simple action to execute. required: true ref: SimpleActionName + named: + description: | + A named action that was defined via the top-level `actions` table or a + `define-action` action. These are usually written as plain strings with a `$` + prefix. + + - Example 1: + + ```toml + [actions] + my-action = "quit" + + [shortcuts] + alt-q = { type = "named", name = "my-action" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-q = [ + { type = "define-action", name = "my-action", action = "quit" }, + { type = "named", name = "my-action" }, + ] + ``` + fields: + name: + kind: string + description: The named action to execute. + required: true multi: description: | A list of actions to execute in sequence. These are usually written as plain @@ -521,6 +567,41 @@ Action: The first matching device is used. required: true ref: DrmDeviceMatch + define-action: + description: | + Defines a name for an action. Usually you would define these by using the + top-level `actions` table. This action can be used to re-define actions. + + - Example: + + ```toml + [actions] + a1 = "quit" + a2 = "$a1" + + [shortcuts] + alt-q = [ + { type = "define-action", name = "a2", action = [] }, + "$2", # does nothing + ] + ``` + fields: + name: + kind: string + description: The name of the action. + required: true + action: + description: The action to execute. + required: true + ref: Action + undefine-action: + description: | + Removes a named action. + fields: + name: + kind: string + description: The name of the action. + required: true Exec: @@ -2350,6 +2431,46 @@ Config: [float] show-pin-icon = true ``` + actions: + kind: map + values: + ref: Action + required: false + description: | + Named actions. + + Named actions can be used everywhere an action can be used. This can be used to + avoid repeating the same action multiple times. + + - Example: + + ```toml + actions.switch-to-1 = [ + { type = "show-workspace", name = "1" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-2" }, + ] + actions.switch-to-2 = [ + { type = "show-workspace", name = "2" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-3" }, + ] + actions.switch-to-3 = [ + { type = "show-workspace", name = "3" }, + { type = "define-action", name = "switch-to-next", action = "$switch-to-1" }, + ] + actions.switch-to-next = "$switch-to-1" + + [shortcuts] + alt-x = "$switch-to-next" + ``` + max-action-depth: + kind: number + integer_only: true + minimum: 0 + required: false + description: | + The maximum call depth of named actions. This setting prevents infinite recursion + when using named actions. Setting this value to 0 or less disables named actions + completely. The default is `16`. Idle: diff --git a/toml-spec/src/json_schema.rs b/toml-spec/src/json_schema.rs index 1efc95f8..67df94e9 100644 --- a/toml-spec/src/json_schema.rs +++ b/toml-spec/src/json_schema.rs @@ -159,6 +159,8 @@ fn create_string_spec(description: &str, spec: &StringSpec) -> Value { if let Some(values) = &spec.values { let strings: Vec<_> = values.iter().map(|v| &v.value.value).collect(); res.insert("enum".into(), json!(strings)); + } else if let Some(pattern) = &spec.pattern { + res.insert("pattern".into(), json!(pattern)); } res.into() } diff --git a/toml-spec/src/markdown.rs b/toml-spec/src/markdown.rs index 5ae8b31d..cac15b0b 100644 --- a/toml-spec/src/markdown.rs +++ b/toml-spec/src/markdown.rs @@ -230,6 +230,12 @@ fn write_string_spec(buf: &mut Vec, spec: &StringSpec, pad: &str) -> Result< writeln!(buf)?; } writeln!(buf)?; + } else if let Some(pattern) = &spec.pattern { + writeln!( + buf, + "{pad}The string should match the following regular expression: `{pattern}`" + )?; + writeln!(buf)?; } Ok(()) } diff --git a/toml-spec/src/types.rs b/toml-spec/src/types.rs index 40b9d1b9..a4aac480 100644 --- a/toml-spec/src/types.rs +++ b/toml-spec/src/types.rs @@ -50,6 +50,7 @@ pub enum RefOrSpec { #[derive(Debug, Deserialize)] pub struct StringSpec { + pub pattern: Option, pub values: Option>>, }