use { crate::{ config::{ Action, SimpleCommand, context::Context, extractor::{Extractor, ExtractorError, arr, bol, n32, opt, s32, str, val}, parser::{DataType, ParseResult, Parser, UnexpectedDataType}, parsers::{ StringParser, StringParserError, connector::{ConnectorParser, ConnectorParserError}, drm_device::{DrmDeviceParser, DrmDeviceParserError}, drm_device_match::{DrmDeviceMatchParser, DrmDeviceMatchParserError}, env::{EnvParser, EnvParserError}, exec::{ExecParser, ExecParserError}, gfx_api::{GfxApiParser, GfxApiParserError}, idle::{IdleParser, IdleParserError}, input::{InputParser, InputParserError}, keymap::{KeymapParser, KeymapParserError}, log_level::{LogLevelParser, LogLevelParserError}, mark_id::{MarkIdParser, MarkIdParserError}, output::{OutputParser, OutputParserError}, output_match::{OutputMatchParser, OutputMatchParserError}, repeat_rate::{RepeatRateParser, RepeatRateParserError}, status::{StatusParser, StatusParserError}, theme::{ThemeParser, ThemeParserError}, }, spanned::SpannedErrorExt, }, toml::{ toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, }, indexmap::IndexMap, jay_config::{ Direction, get_workspace, input::{LayerDirection, Timeline}, }, thiserror::Error, }; #[derive(Debug, Error)] pub enum ActionParserError { #[error(transparent)] Expected(#[from] UnexpectedDataType), #[error(transparent)] StringParser(#[from] StringParserError), #[error("Unknown type {0}")] UnknownType(String), #[error("Unknown simple action {0}")] UnknownSimpleAction(String), #[error(transparent)] Extract(#[from] ExtractorError), #[error("Could not parse the exec action")] Exec(#[source] ExecParserError), #[error("Could not parse the configure-connector action")] ConfigureConnector(#[source] ConnectorParserError), #[error("Could not parse the configure-input action")] ConfigureInput(#[source] InputParserError), #[error("Could not parse the configure-output action")] ConfigureOutput(#[source] OutputParserError), #[error("Could not parse the environment variables")] Env(#[source] EnvParserError), #[error("Could not parse a set-keymap action")] SetKeymap(#[source] KeymapParserError), #[error("Could not parse a set-status action")] Status(#[source] StatusParserError), #[error("Could not parse a set-theme action")] Theme(#[source] ThemeParserError), #[error("Could not parse a set-log-level action")] SetLogLevel(#[source] LogLevelParserError), #[error("Could not parse a set-gfx-api action")] GfxApi(#[source] GfxApiParserError), #[error("Could not parse a configure-drm-device action")] DrmDevice(#[source] DrmDeviceParserError), #[error("Could not parse a set-render-device action")] SetRenderDevice(#[source] DrmDeviceMatchParserError), #[error("Could not parse a configure-idle action")] ConfigureIdle(#[source] IdleParserError), #[error("Could not parse a move-to-output action")] MoveToOutput(#[source] OutputMatchParserError), #[error("Could not parse a set-repeat-rate action")] RepeatRate(#[source] RepeatRateParserError), #[error("Could not parse a create-mark action")] CreateMark(#[source] MarkIdParserError), #[error("Could not parse a jump-to-mark action")] JumpToMark(#[source] MarkIdParserError), #[error("Could not parse a copy-mark action")] CopyMark(#[source] MarkIdParserError), #[error("Could not parse a show-workspace action")] ShowWorkspace(#[source] OutputMatchParserError), #[error("Unknown direction {0}")] UnknownDirection(String), #[error("Exactly one of `output` or `direction` must be specified")] OutputAndDirectionMutuallyExclusive, } 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), "focus-up" => Focus(Up), "focus-right" => Focus(Right), "move-left" => Move(Left), "move-down" => Move(Down), "move-up" => Move(Up), "move-right" => Move(Right), "toggle-fullscreen" => ToggleFullscreen, "enter-fullscreen" => SetFullscreen(true), "exit-fullscreen" => SetFullscreen(false), "send-to-scratchpad" => SendToScratchpad, "toggle-scratchpad" => ToggleScratchpad, "cycle-scratchpad" => CycleScratchpad, "focus-parent" => FocusParent, "close" => Close, "disable-pointer-constraint" => DisablePointerConstraint, "toggle-floating" => ToggleFloating, "float" => SetFloating(true), "tile" => SetFloating(false), "quit" => Quit, "reload-config-toml" => ReloadConfigToml, "reload-config-so" => ReloadConfigSo, "none" => None, "forward" => Forward(true), "consume" => Forward(false), "enable-window-management" => EnableWindowManagement(true), "disable-window-management" => EnableWindowManagement(false), "enable-float-above-fullscreen" => SetFloatAboveFullscreen(true), "disable-float-above-fullscreen" => SetFloatAboveFullscreen(false), "toggle-float-above-fullscreen" => ToggleFloatAboveFullscreen, "pin-float" => SetFloatPinned(true), "unpin-float" => SetFloatPinned(false), "toggle-float-pinned" => ToggleFloatPinned, "kill-client" => KillClient, "show-bar" => ShowBar(true), "hide-bar" => ShowBar(false), "toggle-bar" => ToggleBar, "show-titles" => ShowTitles(true), "hide-titles" => ShowTitles(false), "toggle-titles" => ToggleTitles, "float-titles" => FloatTitles(true), "unfloat-titles" => FloatTitles(false), "toggle-float-titles" => ToggleFloatTitles, "focus-prev" => FocusHistory(Timeline::Older), "focus-next" => FocusHistory(Timeline::Newer), "focus-below" => FocusLayerRel(LayerDirection::Below), "focus-above" => FocusLayerRel(LayerDirection::Above), "focus-tiles" => FocusTiles, "toggle-float-focus" => ToggleFocusFloatTiled, "create-mark" => CreateMark, "jump-to-mark" => JumpToMark, "clear-modes" => PopMode(false), "pop-mode" => PopMode(true), "enable-simple-im" => EnableSimpleIm(true), "disable-simple-im" => EnableSimpleIm(false), "toggle-simple-im-enabled" => ToggleSimpleImEnabled, "reload-simple-im" => ReloadSimpleIm, "enable-unicode-input" => EnableUnicodeInput, "warp-mouse-to-focus" => WarpMouseToFocus, "toggle-tab" => ToggleTab, "make-group-h" => MakeGroupH, "make-group-v" => MakeGroupV, "make-group-tab" => MakeGroupTab, "change-group-opposite" => ChangeGroupOpposite, "equalize" => Equalize, "equalize-recursive" => EqualizeRecursive, "move-tab-left" => MoveTabLeft, "move-tab-right" => MoveTabRight, "enable-autotile" => SetAutotile(true), "disable-autotile" => SetAutotile(false), "toggle-autotile" => ToggleAutotile, _ => { return Err( ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span) ); } }; Ok(Action::SimpleCommand { cmd }) } fn parse_multi(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { let mut actions = vec![]; for el in array { actions.push(el.parse(self)?); } Ok(Action::Multi { actions }) } fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let exec = ext .extract(val("exec"))? .parse_map(&mut ExecParser(self.0)) .map_spanned_err(ActionParserError::Exec)?; Ok(Action::Exec { exec }) } fn parse_switch_to_vt(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let num = ext.extract(n32("num"))?.value; Ok(Action::SwitchToVt { num }) } fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (name, output) = ext.extract((str("name"), opt(val("output"))))?; let name = name.value.to_string(); let output = output .map(|o| { o.parse_map(&mut OutputMatchParser(self.0)) .map_spanned_err(ActionParserError::ShowWorkspace) }) .transpose()?; Ok(Action::ShowWorkspace { name, output }) } fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let name = ext.extract(str("name"))?.value.to_string(); Ok(Action::MoveToWorkspace { name }) } fn parse_send_to_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let name = ext .extract(opt(str("name")))? .map(|name| name.value) .unwrap_or("") .to_string(); Ok(Action::SendToScratchpad { name }) } fn parse_toggle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let name = ext .extract(opt(str("name")))? .map(|name| name.value) .unwrap_or("") .to_string(); Ok(Action::ToggleScratchpad { name }) } fn parse_cycle_scratchpad(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let name = ext .extract(opt(str("name")))? .map(|name| name.value) .unwrap_or("") .to_string(); Ok(Action::CycleScratchpad { name }) } fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let con = ext .extract(val("connector"))? .parse_map(&mut ConnectorParser(self.0)) .map_spanned_err(ActionParserError::ConfigureConnector)?; Ok(Action::ConfigureConnector { con }) } fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let input = ext .extract(val("input"))? .parse_map(&mut InputParser { cx: self.0, is_inputs_array: false, }) .map_spanned_err(ActionParserError::ConfigureInput)?; Ok(Action::ConfigureInput { input: Box::new(input), }) } fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let idle = ext .extract(val("idle"))? .parse_map(&mut IdleParser(self.0)) .map_spanned_err(ActionParserError::ConfigureIdle)?; Ok(Action::ConfigureIdle { idle: idle.timeout, grace_period: idle.grace_period, }) } fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let out = ext .extract(val("output"))? .parse_map(&mut OutputParser { cx: self.0, name_ok: false, }) .map_spanned_err(ActionParserError::ConfigureOutput)?; Ok(Action::ConfigureOutput { out }) } fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let env = ext .extract(val("env"))? .parse_map(&mut EnvParser) .map_spanned_err(ActionParserError::Env)?; Ok(Action::SetEnv { env }) } fn parse_unset_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult { struct P; impl Parser for P { type Value = Vec; type Error = ActionParserError; const EXPECTED: &'static [DataType] = &[DataType::Array, DataType::String]; fn parse_string(&mut self, _span: Span, string: &str) -> ParseResult { Ok(vec![string.to_string()]) } fn parse_array(&mut self, _span: Span, array: &[Spanned]) -> ParseResult { let mut res = vec![]; for v in array { res.push(v.parse_map(&mut StringParser)?); } Ok(res) } } let env = ext.extract(val("env"))?.parse_map(&mut P)?; Ok(Action::UnsetEnv { env }) } fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let map = ext .extract(val("map"))? .parse_map(&mut KeymapParser { cx: self.0, definition: false, }) .map_spanned_err(ActionParserError::SetKeymap)?; Ok(Action::SetKeymap { map }) } fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let status = match ext.extract(opt(val("status")))? { None => None, Some(v) => Some( v.parse_map(&mut StatusParser(self.0)) .map_spanned_err(ActionParserError::Status)?, ), }; Ok(Action::SetStatus { status }) } fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let theme = ext .extract(val("theme"))? .parse_map(&mut ThemeParser(self.0)) .map_spanned_err(ActionParserError::Theme)?; Ok(Action::SetTheme { theme: Box::new(theme), }) } fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let level = ext .extract(val("level"))? .parse_map(&mut LogLevelParser) .map_spanned_err(ActionParserError::SetLogLevel)?; Ok(Action::SetLogLevel { level }) } fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let api = ext .extract(val("api"))? .parse_map(&mut GfxApiParser) .map_spanned_err(ActionParserError::GfxApi)?; Ok(Action::SetGfxApi { api }) } fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let dev = ext .extract(val("dev"))? .parse_map(&mut DrmDeviceMatchParser(self.0)) .map_spanned_err(ActionParserError::SetRenderDevice)?; Ok(Action::SetRenderDevice { dev: Box::new(dev) }) } fn parse_configure_direct_scanout(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let enabled = ext.extract(bol("enabled"))?.value; Ok(Action::ConfigureDirectScanout { enabled }) } fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let dev = ext .extract(val("dev"))? .parse_map(&mut DrmDeviceParser { cx: self.0, name_ok: false, }) .map_spanned_err(ActionParserError::DrmDevice)?; Ok(Action::ConfigureDrmDevice { dev }) } fn parse_direction(v: Spanned<&str>) -> Result> { use Direction::*; match v.value { "left" => Ok(Left), "right" => Ok(Right), "up" => Ok(Up), "down" => Ok(Down), _ => Err(ActionParserError::UnknownDirection(v.value.to_string()).spanned(v.span)), } } fn parse_move_to_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (ws, output_val, direction_val) = ext.extract(( opt(str("workspace")), opt(val("output")), opt(str("direction")), ))?; // Validate that exactly one of output or direction is specified if output_val.is_some() == direction_val.is_some() { return Err(ActionParserError::OutputAndDirectionMutuallyExclusive.spanned(ext.span())); } let output = output_val .map(|v| { v.parse(&mut OutputMatchParser(self.0)) .map_spanned_err(ActionParserError::MoveToOutput) }) .transpose()?; let direction = direction_val.map(Self::parse_direction).transpose()?; Ok(Action::MoveToOutput { workspace: ws.despan().map(get_workspace), output, direction, }) } fn parse_set_repeat_rate(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let rate = ext .extract(val("rate"))? .parse_map(&mut RepeatRateParser(self.0)) .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(), }) } fn parse_create_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (id,) = ext.extract((opt(val("id")),))?; let Some(id) = id else { return Ok(Action::SimpleCommand { cmd: SimpleCommand::CreateMark, }); }; let id = id .parse(&mut MarkIdParser(self.0)) .map_spanned_err(ActionParserError::CreateMark)?; Ok(Action::CreateMark(id)) } fn parse_jump_to_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (id,) = ext.extract((opt(val("id")),))?; let Some(id) = id else { return Ok(Action::SimpleCommand { cmd: SimpleCommand::JumpToMark, }); }; let id = id .parse(&mut MarkIdParser(self.0)) .map_spanned_err(ActionParserError::JumpToMark)?; Ok(Action::JumpToMark(id)) } fn parse_copy_mark(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (src, dst) = ext.extract((val("src"), val("dst")))?; let src = src .parse(&mut MarkIdParser(self.0)) .map_spanned_err(ActionParserError::CopyMark)?; let dst = dst .parse(&mut MarkIdParser(self.0)) .map_spanned_err(ActionParserError::CopyMark)?; Ok(Action::CopyMark(src, dst)) } fn parse_push_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (name,) = ext.extract((str("name"),))?; Ok(Action::SetMode { name: name.value.to_string(), latch: false, }) } fn parse_latch_mode(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (name,) = ext.extract((str("name"),))?; Ok(Action::SetMode { name: name.value.to_string(), latch: true, }) } fn parse_create_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (name,) = ext.extract((str("name"),))?; Ok(Action::CreateVirtualOutput { name: name.value.to_string(), }) } fn parse_remove_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (name,) = ext.extract((str("name"),))?; Ok(Action::RemoveVirtualOutput { name: name.value.to_string(), }) } fn parse_resize(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let (dx1, dy1, dx2, dy2) = ext.extract(( opt(s32("dx1")), opt(s32("dy1")), opt(s32("dx2")), opt(s32("dy2")), ))?; Ok(Action::Resize { dx1: dx1.despan().unwrap_or(0), dy1: dy1.despan().unwrap_or(0), dx2: dx2.despan().unwrap_or(0), dy2: dy2.despan().unwrap_or(0), }) } } impl Parser for ActionParser<'_> { type Value = Action; type Error = ActionParserError; const EXPECTED: &'static [DataType] = &[DataType::String, DataType::Array, DataType::Table]; fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { self.parse_simple_cmd(span, string) } fn parse_array(&mut self, span: Span, array: &[Spanned]) -> ParseResult { self.parse_multi(span, array) } fn parse_table( &mut self, span: Span, table: &IndexMap, Spanned>, ) -> ParseResult { let mut ext = Extractor::new(self.0, span, table); let ty = ext.extract_or_ignore(str("type"))?; let res = match ty.value { "simple" => { let cmd = ext.extract(str("cmd"))?; self.parse_simple_cmd(cmd.span, cmd.value) } "multi" => { let actions = ext.extract(arr("actions"))?; self.parse_multi(actions.span, actions.value) } "exec" => self.parse_exec(&mut ext), "switch-to-vt" => self.parse_switch_to_vt(&mut ext), "show-workspace" => self.parse_show_workspace(&mut ext), "move-to-workspace" => self.parse_move_to_workspace(&mut ext), "send-to-scratchpad" => self.parse_send_to_scratchpad(&mut ext), "toggle-scratchpad" => self.parse_toggle_scratchpad(&mut ext), "cycle-scratchpad" => self.parse_cycle_scratchpad(&mut ext), "configure-connector" => self.parse_configure_connector(&mut ext), "configure-input" => self.parse_configure_input(&mut ext), "configure-output" => self.parse_configure_output(&mut ext), "set-env" => self.parse_set_env(&mut ext), "unset-env" => self.parse_unset_env(&mut ext), "set-keymap" => self.parse_set_keymap(&mut ext), "set-status" => self.parse_set_status(&mut ext), "set-theme" => self.parse_set_theme(&mut ext), "set-log-level" => self.parse_set_log_level(&mut ext), "set-gfx-api" => self.parse_set_gfx_api(&mut ext), "configure-direct-scanout" => self.parse_configure_direct_scanout(&mut ext), "configure-drm-device" => self.parse_configure_drm_device(&mut ext), "set-render-device" => self.parse_set_render_device(&mut ext), "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), "create-mark" => self.parse_create_mark(&mut ext), "jump-to-mark" => self.parse_jump_to_mark(&mut ext), "copy-mark" => self.parse_copy_mark(&mut ext), "push-mode" => self.parse_push_mode(&mut ext), "latch-mode" => self.parse_latch_mode(&mut ext), "create-virtual-output" => self.parse_create_virtual_output(&mut ext), "remove-virtual-output" => self.parse_remove_virtual_output(&mut ext), "resize" => self.parse_resize(&mut ext), v => { ext.ignore_unused(); return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); } }; drop(ext); res } }