use crate::{app::Node, input::InputOperation}; use indexmap::IndexSet; use regex::Regex; use serde::{Deserialize, Serialize}; use std::cmp::Ordering; #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum ExternalMsg { /// ### Exploring ---------------------------------------------------------- /// Explore the present working directory and register the filtered nodes. /// This operation is expensive. So, try to avoid using it too often. /// /// Example: /// /// - Lua: `"ExplorePwd"` /// - YAML: `ExplorePwd` ExplorePwd, /// Explore the present working directory and register the filtered nodes /// asynchronously. This operation happens asynchronously. That means, the /// xplr directory buffers won't be updated immediately. Hence, it needs to /// be used with care and probably with special checks in place. To explore /// $PWD synchronously, use `ExplorePwd` instead. /// /// Example: /// /// - Lua: `"ExplorePwdAsync"` /// - YAML: `ExplorePwdAsync` ExplorePwdAsync, /// Explore the present working directory along with its parents and /// register the filtered nodes. This operation happens asynchronously. /// That means, the xplr directory buffers won't be updated immediately. /// Hence, it needs to be used with care and probably with special checks /// in place. To explore just the `$PWD` synchronously, use `ExplorePwd` /// instead. /// /// Example: /// /// - Lua: `"ExploreParentsAsync"` /// - YAML: `ExploreParentsAsync` ExploreParentsAsync, /// ### Screen ------------------------------------------------------------- /// Clear the screen. /// /// Example: /// /// - Lua: `"ClearScreen"` /// - YAML: `ClearScreen` ClearScreen, /// Refresh the screen. /// But it will not re-explore the directory if the working directory is /// the same. If there is some change in the working directory and you want /// to re-explore it, use the `Explore` message instead. /// Also, it will not clear the screen. Use `ClearScreen` for that. /// /// Example: /// /// - Lua: `"Refresh"` /// - YAML: `Refresh` Refresh, /// ### Navigation --------------------------------------------------------- /// Focus next node. /// /// Example: /// /// - Lua: `"FocusNext"` /// - YAML: `FocusNext` FocusNext, /// Focus on the `n`th node relative to the current focus where `n` is a /// given value. /// /// Type: { FocusNextByRelativeIndex = int } /// /// Example: /// /// - Lua: `{ FocusNextByRelativeIndex = 2 }` /// - YAML: `FocusNextByRelativeIndex: 2` FocusNextByRelativeIndex(usize), /// Focus on the `n`th node relative to the current focus where `n` is read /// from the input buffer. /// /// Example: /// /// - Lua: `"FocusNextByRelativeIndexFromInput"` /// - YAML: `FocusNextByRelativeIndexFromInput` FocusNextByRelativeIndexFromInput, /// Focus on the previous item. /// /// Example: /// /// - Lua: `"FocusPrevious"` /// - YAML: `FocusPrevious` FocusPrevious, /// Focus on the `-n`th node relative to the current focus where `n` is a /// given value. /// /// Type: { FocusPreviousByRelativeIndex = int } /// /// Example: /// /// - Lua: `{ FocusPreviousByRelativeIndex = 2 }` /// - YAML: `FocusPreviousByRelativeIndex: 2` FocusPreviousByRelativeIndex(usize), /// Focus on the `-n`th node relative to the current focus where `n` is /// read from the input buffer. /// /// Example: /// /// - Lua: `"FocusPreviousByRelativeIndexFromInput"` /// - YAML: `FocusPreviousByRelativeIndexFromInput` FocusPreviousByRelativeIndexFromInput, /// Focus on the first node. /// /// Example: /// /// - Lua: `"FocusFirst"` /// - YAML: `FocusFirst` /// FocusFirst, /// Focus on the last node. /// /// Example: /// - Lua: `"FocusLast"` /// - YAML: `FocusLast` FocusLast, /// Focus on the given path. /// /// Type: { FocusPath = "string" } /// /// Example: /// /// - Lua: `{ FocusPath = "/path/to/file" }` /// - YAML: `FocusPath: /path/to/file` FocusPath(String), /// Focus on the path read from input buffer. /// /// Example: /// /// - Lua: `"FocusPathFromInput"` /// - YAML: `FocusPathFromInput` FocusPathFromInput, /// Focus on the absolute `n`th node where `n` is a given value. /// /// Type: { FocusByIndex = int } /// /// Example: /// /// - Lua: `{ FocusByIndex = 2 }` /// - YAML: `FocusByIndex: 2` FocusByIndex(usize), /// Focus on the absolute `n`th node where `n` is read from the input buffer. /// /// Example: /// /// - Lua: `"FocusByIndexFromInput"` /// - YAML: `FocusByIndexFromInput` FocusByIndexFromInput, /// /// **YAML:** `FocusByFileName: string` /// /// Focus on the file by name from the present working directory. /// /// Type: { FocusByFileName = "string" } /// /// Example: /// /// - Lua: `{ FocusByFileName = "filename.ext" }` /// - YAML: `FocusByFileName: filename.ext` FocusByFileName(String), /// Change the present working directory ($PWD) /// /// Type: { ChangeDirectory = "string" } /// /// Example: /// /// - Lua: `{ ChangeDirectory = "/path/to/directory" }` /// - YAML: `ChangeDirectory: /path/to/directory` ChangeDirectory(String), /// Enter into the currently focused path if it's a directory. /// /// Example: /// /// - Lua: `"Enter"` /// - YAML: `Enter` Enter, /// Go back to the parent directory. /// /// Example: /// /// - Lua: `"Back"` /// - YAML: `Back` Back, /// Go to the last path visited. /// /// Example: /// /// - Lua: `"LastVisitedPath"` /// - YAML: `LastVisitedPath` LastVisitedPath, /// Go to the next path visited. /// /// Example: /// /// - Lua: `"NextVisitedPath"` /// - YAML: `NextVisitedPath` NextVisitedPath, /// /// Follow the symlink under focus to its actual location. /// /// Example: /// /// Lua: `"FollowSymlink"` /// YAML: `FollowSymlink` FollowSymlink, /// ### Reading Input ----------------------------------------------------- /// Set the input prompt temporarily, until the input buffer is reset. /// /// Type: { SetInputPrompt = string } /// /// Example: /// /// - Lua: `{ SetInputPrompt = "→" }` /// - YAML: `SetInputPrompt: →` SetInputPrompt(String), /// Update the input buffer using cursor based operations. /// /// Type: { UpdateInputBuffer = [Input Opertaion](https://xplr.dev/en/input-operation) } /// /// Example: /// /// - Lua: `{ UpdateInputBuffer = "GoToPreviousWord" }` /// - YAML: `UpdateInputBuffer: GoToPreviousWord` UpdateInputBuffer(InputOperation), /// Update the input buffer from the key read from keyboard input. /// /// Example: /// /// - Lua: `"UpdateInputBufferFromKey"` /// - YAML: `UpdateInputBufferFromKey` UpdateInputBufferFromKey, /// Append/buffer the given string into the input buffer. /// /// Type: { BufferInput = "string" } /// /// Example: /// /// - Lua: `{ BufferInput = "foo" }` /// - YAML: `BufferInput: foo` BufferInput(String), /// Append/buffer the characted read from a keyboard input into the /// input buffer. /// /// Example: /// /// - Lua: `"BufferInputFromKey"` /// - YAML: `BufferInputFromKey` BufferInputFromKey, /// Set/rewrite the input buffer with the given string. /// When the input buffer is not-null (even if empty string) /// it will show in the UI. /// /// Type: { SetInputBuffer = "string" } /// /// Example: /// /// - Lua: `{ SetInputBuffer = "foo" }` /// - YAML: `SetInputBuffer: foo` SetInputBuffer(String), /// Remove input buffer's last character. /// /// Example: /// /// - Lua: `"RemoveInputBufferLastCharacter"` /// - YAML: `RemoveInputBufferLastCharacter` RemoveInputBufferLastCharacter, /// Remove input buffer's last word. /// /// Example: /// /// - Lua: `"RemoveInputBufferLastWord"` /// - YAML: `RemoveInputBufferLastWord` RemoveInputBufferLastWord, /// Reset the input buffer back to null. It will not show in the UI. /// /// Example: /// /// - Lua: `"ResetInputBuffer"` /// - YAML: `ResetInputBuffer` ResetInputBuffer, /// ### Switching Mode ----------------------------------------------------- /// Switch input [mode](https://xplr.dev/en/modes). /// /// Type : { SwitchMode = "string" } /// /// Example: /// /// - Lua: `{ SwitchMode = "default" }` /// - YAML: SwitchMode: default /// /// > **NOTE:** To be specific about which mode to switch to, use /// > `SwitchModeBuiltinKeepingInputBuffer` or /// > `SwitchModeCustomKeepingInputBuffer` instead. SwitchMode(String), /// Switch input [mode](https://xplr.dev/en/modes). /// It keeps the input buffer. /// /// Type: { SwitchModeKeepingInputBuffer = "string" } /// /// Example: /// /// - Lua: `{ SwitchModeKeepingInputBuffer = "default" }` /// - YAML: `SwitchModeKeepingInputBuffer: default` /// /// > **NOTE:** To be specific about which mode to switch to, use /// > `SwitchModeBuiltinKeepingInputBuffer` or /// > `SwitchModeCustomKeepingInputBuffer` instead. SwitchModeKeepingInputBuffer(String), /// Switch to a [builtin mode](https://xplr.dev/en/modes#builtin). /// It clears the input buffer. /// /// Type: { SwitchModeBuiltin = "string" } /// /// Example: /// /// - Lua: `{ SwitchModeBuiltin = "default" }` /// - YAML: `SwitchModeBuiltin: default` SwitchModeBuiltin(String), /// Switch to a [builtin mode](https://xplr.dev/en/modes#builtin). /// It keeps the input buffer. /// /// Type: { SwitchModeBuiltinKeepingInputBuffer = "string" } /// /// Example: /// /// - Lua: `{ SwitchModeBuiltinKeepingInputBuffer = "default" }` /// - YAML: `SwitchModeBuiltinKeepingInputBuffer: default` SwitchModeBuiltinKeepingInputBuffer(String), /// Switch to a [custom mode](https://xplr.dev/en/modes#custom). /// It clears the input buffer. /// /// Type: { SwitchModeCustom = "string" } /// /// Example: /// /// - Lua: `{ SwitchModeCustom = "my_custom_mode" }` /// - YAML: `SwitchModeCustom: my_custom_mode` SwitchModeCustom(String), /// Switch to a [custom mode](https://xplr.dev/en/modes#custom). /// It keeps the input buffer. /// /// Type: { SwitchModeCustomKeepingInputBuffer = "string" } /// /// Example: /// /// - Lua: `{ SwitchModeCustomKeepingInputBuffer = "my_custom_mode" }` /// - YAML: `SwitchModeCustomKeepingInputBuffer: my_custom_mode` SwitchModeCustomKeepingInputBuffer(String), /// Pop the last mode from the history and switch to it. /// It clears the input buffer. /// /// Example: /// /// - Lua: `"PopMode"` /// - YAML: `PopMode` PopMode, /// Pop the last mode from the history and switch to it. /// It keeps the input buffer. /// /// Example: /// /// - Lua: `PopModeKeepingInputBuffer` /// - YAML: `PopModeKeepingInputBuffer` PopModeKeepingInputBuffer, /// ### Switching Layout --------------------------------------------------- /// Switch [layout](https://xplr.dev/en/layouts). /// /// Type: { SwitchLayout = "string" } /// /// Example: /// /// - Lua: `{ SwitchLayout = "default" }` /// - YAML: `SwitchLayout: default` /// /// > **NOTE:** To be specific about which layout to switch to, use `SwitchLayoutBuiltin` or /// > `SwitchLayoutCustom` instead. SwitchLayout(String), /// Switch to a [builtin layout](https://xplr.dev/en/layouts#builtin). /// /// Type: { SwitchLayoutBuiltin = "string" } /// /// Example: /// /// - Lua: `{ SwitchLayoutBuiltin = "default" }` /// - YAML: `SwitchLayoutBuiltin: default` SwitchLayoutBuiltin(String), /// Switch to a [custom layout](https://xplr.dev/en/layouts#custom). /// /// Type: { SwitchLayoutCustom = "string" } /// /// Example: /// /// - Lua: `{ SwitchLayoutCustom = "my_custom_layout" }` /// - YAML: `SwitchLayoutCustom: my_custom_layout` SwitchLayoutCustom(String), /// ### Executing Commands ------------------------------------------------ /// Call a shell command with the given arguments. /// Note that the arguments will be shell-escaped. /// So to read the variables, the `-c` option of the shell /// can be used. /// You may need to pass `ExplorePwd` depening on the expectation. /// /// Type: { Call = "string" } /// /// Example: /// /// - Lua: `{ Call = { command = "bash", args = { "-c", "read -p test" } } }` /// - YAML: `Call: { command: bash, args: ["-c", "read -p test"] }` Call(Command), /// Like `Call` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { CallSilently = "string" } /// /// Example: /// /// - Lua: `{ CallSilently = { command = "tput", args = { "bell" } } }` /// - YAML: `CallSilently: { command: tput, args: ["bell"] }` CallSilently(Command), /// An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}` /// where `{string}` is the given value. /// /// Type: { BashExec = "string" } /// /// Example: /// /// - Lua: `{ BashExec = "read -p test" }` /// - YAML: `BashExec: "read -p test"` BashExec(String), /// Like `BashExec` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { BashExecSilently = "string" } /// /// Example: /// /// - Lua: `{ BashExecSilently = "tput bell" }` /// - YAML: `BashExecSilently: "tput bell"` BashExecSilently(String), /// ### Calling Lua Functions ---------------------------------------------- /// Call a Lua function. /// /// A [Lua Context](https://xplr.dev/en/lua-function-calls#lua-context) /// object will be passed to the function as argument. /// The function can optionally return a list of messages for xplr to /// handle after the executing the function. /// /// Type: { CallLua = "string" } /// /// Example: /// /// - Lua: `{ CallLua = "custom.some_custom_funtion" }` /// - YAML: `CallLua: custom.some_custom_funtion` CallLua(String), /// Like `CallLua` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { CallLuaSilently = "string" } /// /// Example: /// /// - Lua: `{ CallLuaSilently = "custom.some_custom_function" }` /// - YAML: `CallLuaSilently: custom.some_custom_function` CallLuaSilently(String), /// Execute Lua code without needing to define a function. /// /// If the `string` is a callable, xplr will try to call it with with the /// [Lua Context](https://xplr.dev/en/lua-function-calls#lua-context) /// argument. /// /// Type: { LuaEval = "string" } /// /// Example: /// /// - Lua: `{ LuaEval = [[return { { LogInfo = io.read() } }]] }` /// - Lua: `{ LuaEval = [[function(app) return { { LogInfo = app.pwd } } end]] }` /// - YAML: `LuaEval: "return { { LogInfo = io.read() } }"` /// - YAML: `LuaEval: "function(app) return { { LogInfo = app.pwd } } end"` LuaEval(String), /// Like `LuaEval` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { LuaEvalSilently = "string" } /// /// Example: /// /// - Lua: `{ LuaEvalSilently = [[return { { LogInfo = "foo" } }]] }` /// - YAML: `LuaEvalSilently: "return { { LogInfo = 'foo' } }"` LuaEvalSilently(String), /// ### Select Operations -------------------------------------------------- /// Select the focused node. /// /// Example: /// /// - Lua: `"Select"` /// - YAML: `Select` Select, /// Select all the visible nodes. /// /// Example: /// /// - Lua: `"SelectAll"` /// - YAML: `SelectAll` SelectAll, /// Select the given path. /// /// Type: { SelectPath = "string" } /// /// Example: /// /// - Lua: `{ SelectPath = "/path/to/file" }` /// - YAML: `SelectPath: /path/to/file` SelectPath(String), /// Unselect the focused node. /// /// Example: /// /// - Lua: `"UnSelect"` /// - YAML: `UnSelect` UnSelect, /// Unselect all the visible nodes. /// /// Example: /// /// - Lua: `"UnSelectAll"` /// - YAML: `UnSelectAll` UnSelectAll, /// UnSelect the given path. /// /// Type: { UnSelectPath = "string)" } /// /// Example: /// /// - Lua: `{ UnSelectPath = "/path/to/file" }` /// - YAML: `UnSelectPath: /path/to/file` UnSelectPath(String), /// Toggle selection on the focused node. /// /// Example: /// /// - Lua: `"ToggleSelection"` /// - YAML `ToggleSelection` ToggleSelection, /// Toggle between select all and unselect all. /// Example: /// /// - Lua: `"ToggleSelectAll"` /// - YAML: `ToggleSelectAll` ToggleSelectAll, /// Toggle selection by file path. /// /// Type: { ToggleSelectionByPath = "string" } /// /// Example: /// /// - Lua: `{ ToggleSelectionByPath = "/path/to/file" }` /// - YAML: `ToggleSelectionByPath: /path/to/file` ToggleSelectionByPath(String), /// Clear the selection. /// /// Example: /// /// - Lua: `"ClearSelection"` /// - YAML: `ClearSelection` ClearSelection, /// ### Filter Operations -------------------------------------------------- /// Add a [filter](https://xplr.dev/en/filtering#filter) to exclude nodes /// while exploring directories. /// /// Type: { AddNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering#filter), input = "string" } /// /// Example: /// /// - Lua: `{ AddNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }` /// - YAML: `AddNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` AddNodeFilter(NodeFilterApplicable), /// Remove an existing [filter](https://xplr.dev/en/filtering#filter). /// /// Type: { RemoveNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } /// /// Example: /// /// - Lua: `{ RemoveNodeFilter: { filter: "RelativePathDoesStartWith", input: "foo" } }` /// - YAML: `RemoveNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` RemoveNodeFilter(NodeFilterApplicable), /// Remove a [filter](https://xplr.dev/en/filtering#filter) if it exists, /// else, add a it. /// /// Type: { ToggleNodeFilter = { filter = [Filter](https://xplr.dev/en/filtering), input = "string" } /// /// Example: /// /// - Lua: `{ ToggleNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }` /// - YAML: `ToggleNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` ToggleNodeFilter(NodeFilterApplicable), /// Add a node [filter](https://xplr.dev/en/filtering#filter) reading the /// input from the buffer. /// /// Type: { AddNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } /// /// Example: /// /// - Lua: `{ AddNodeFilterFromInput = "RelativePathDoesStartWith" }` /// - YAML: `AddNodeFilterFromInput: RelativePathDoesStartWith` AddNodeFilterFromInput(NodeFilter), /// Remove a node [filter](https://xplr.dev/en/filtering#filter) reading /// the input from the buffer. /// /// Type: { RemoveNodeFilterFromInput = [Filter](https://xplr.dev/en/filtering) } /// /// Example: /// /// - Lua: `{ RemoveNodeFilterFromInput = "RelativePathDoesStartWith" }` /// - YAML: `RemoveNodeFilterFromInput: RelativePathDoesStartWith` RemoveNodeFilterFromInput(NodeFilter), /// Remove the last node [filter](https://xplr.dev/en/filtering). /// /// Example: /// /// - Lua: `"RemoveLastNodeFilter"` /// - YAML: `RemoveLastNodeFilter` RemoveLastNodeFilter, /// Reset the node [filters](https://xplr.dev/en/filtering) back to the /// default configuration. /// /// Example: /// /// - Lua: `"ResetNodeFilters"` /// - YAML: `ResetNodeFilters` ResetNodeFilters, /// Clear all the node [filters](https://xplr.dev/en/filtering). /// /// Example: /// /// - Lua: `"ClearNodeFilters"` /// - YAML: `ClearNodeFilters` ClearNodeFilters, /// ### Sort Operations ---------------------------------------------------- /// Add a [sorter](https://xplr.dev/en/sorting#sorter) to sort nodes while /// exploring directories. /// /// Type: { AddNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } /// /// Example: /// /// - Lua: `{ AddNodeSorter = { sorter = "ByRelativePath", reverse = false } }` /// - YAML: `AddNodeSorter: { sorter: ByRelativePath, reverse: false }` AddNodeSorter(NodeSorterApplicable), /// Remove an existing [sorter](https://xplr.dev/en/sorting#sorter). /// /// Type: { RemoveNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } /// /// Example: /// /// - Lua: `{ RemoveNodeSorter = "ByRelativePath" }` /// - YAML: `RemoveNodeSorter: ByRelativePath` RemoveNodeSorter(NodeSorter), /// Reverse a node [sorter](https://xplr.dev/en/sorting#sorter). /// /// Type: { ReverseNodeSorter = [Sorter](https://xplr.dev/en/sorting#sorter) } /// /// Example: /// /// - Lua: `{ ReverseNodeSorter = "ByRelativePath" }` /// - YAML: `ReverseNodeSorter: ByRelativePath` ReverseNodeSorter(NodeSorter), /// Remove a [sorter](https://xplr.dev/en/sorting#sorter) if it exists, /// else, add a it. /// /// Type: { ToggleNodeSorter = { sorter = [Sorter](https://xplr.dev/en/sorting#sorter), reverse = bool } } /// /// Example: /// /// - Lua: `{ ToggleSorterSorter: { sorter = "ByRelativePath", reverse = false } }` /// - YAML: `ToggleSorterSorter: {sorter: ByRelativePath, reverse: false }` ToggleNodeSorter(NodeSorterApplicable), /// Reverse the node [sorters](https://xplr.dev/en/sorting#sorter). /// /// Example: /// /// - Lua: `"ReverseNodeSorters"` /// - YAML: `ReverseNodeSorters` ReverseNodeSorters, /// Remove the last node [sorter](https://xplr.dev/en/sorting#sorter). /// /// Example: /// /// - Lua: `"RemoveLastNodeSorter"` /// - YAML: `RemoveLastNodeSorter` RemoveLastNodeSorter, /// Reset the node [sorters](https://xplr.dev/en/sorting#sorter) back to /// the default configuration. /// /// Example: /// /// - Lua: `"ResetNodeSorters"` /// - YAML: `ResetNodeSorters` ResetNodeSorters, /// Clear all the node [sorters](https://xplr.dev/en/sorting#sorter). /// /// Example: /// /// - Lua: `"ClearNodeSorters"` /// - YAML: `ClearNodeSorters` ClearNodeSorters, /// ### Mouse Operations --------------------------------------------------- /// Enable mouse /// /// Example: /// /// - Lua: `"EnableMouse"` /// - YAML: `EnableMouse` EnableMouse, /// Disable mouse /// /// Example: /// /// - Lua: `"DisableMouse"` /// - YAML: `DisableMouse` DisableMouse, /// Toggle mouse /// /// Example: /// /// - Lua: `"ToggleMouse"` /// - YAML: `ToggleMouse` ToggleMouse, /// ### Fifo Operations ---------------------------------------------------- /// Start piping the focused path to the given fifo path /// /// Type: { StartFifo = "string" } /// /// Example: /// /// - Lua: `{ StartFifo = "/tmp/xplr.fifo }` /// - YAML: `StartFifo: /tmp/xplr.fifo` StartFifo(String), /// Close the active fifo and stop piping. /// /// Example: /// /// - Lua: `"StopFifo"` /// - YAML: `StopFifo` StopFifo, /// Toggle betwen {Start|Stop}Fifo /// /// Type: { ToggleFifo = "string" } /// /// Example: /// /// - Lua: `{ ToggleFifo = "/path/to/fifo" }` /// - YAML: `ToggleFifo: /path/to/fifo` ToggleFifo(String), /// ### Logging ------------------------------------------------------------ /// Log information message. /// /// Type: { LogInfo = "string" } /// /// Example: /// /// - Lua: `{ LogInfo = "launching satellite" }` /// - YAML: `LogInfo: launching satellite` LogInfo(String), /// Log a success message. /// /// Type: { LogSuccess = "String" } /// /// Example: /// /// - Lua: `{ LogSuccess = "satellite reached destination" }` /// - YAML: `LogSuccess: satellite reached destination` LogSuccess(String), /// Log an warning message. /// /// Type: { LogWarning = "string" } /// /// Example: /// /// - Lua: `{ LogWarning = "satellite is heating" }` /// - YAML: `LogWarning: satellite is heating` LogWarning(String), /// Log an error message. /// /// Type: { LogError = "string" } /// /// Example: /// /// - Lua: `{ LogError = "satellite crashed" }` /// - YAML: `LogError: satellite crashed` LogError(String), /// ### Debugging ---------------------------------------------------------- /// Write the application state to a file, without quitting. Also helpful /// for debugging. /// /// Type: { Debug = "string" } /// /// Example: /// /// - Lua: `{ Debug = "/path/to/file" }` /// - YAML: `Debug: /path/to/file` Debug(String), /// ### Quit Options ------------------------------------------------------- /// Example: /// /// - Lua: `"Quit"` /// - YAML: `Quit` /// /// Quit with returncode zero (success). Quit, /// Print $PWD and quit. /// /// Example: /// /// - Lua: `"PrintPwdAndQuit"` /// - YAML: `PrintPwdAndQuit` PrintPwdAndQuit, /// Print the path under focus and quit. It can be empty string if there's /// nothing to focus. /// /// Example: /// /// - Lua: `"PrintFocusPathAndQuit"` /// - YAML: `PrintFocusPathAndQuit` PrintFocusPathAndQuit, /// Print the selected paths and quit. It can be empty is no path is /// selected. /// /// Example: /// /// - Lua: `"PrintSelectionAndQuit"` /// - YAML: `PrintSelectionAndQuit` PrintSelectionAndQuit, /// Print the selected paths if it's not empty, else, print the focused /// node's path. /// /// Example: /// /// - Lua: `"PrintResultAndQuit"` /// - YAML: `PrintResultAndQuit` PrintResultAndQuit, /// Print the state of application in YAML format. Helpful for debugging or /// generating the default configuration file. /// /// Example: /// /// - Lua: `"PrintAppStateAndQuit"` /// - YAML: `PrintAppStateAndQuit` PrintAppStateAndQuit, /// Terminate the application with a non-zero return code. /// /// Example: /// /// - Lua: `"Terminate"` /// - YAML: `Terminate` Terminate, } impl ExternalMsg { pub fn is_read_only(&self) -> bool { !matches!( self, Self::Call(_) | Self::CallSilently(_) | Self::BashExec(_) | Self::BashExecSilently(_) | Self::CallLua(_) | Self::CallLuaSilently(_) | Self::LuaEval(_) | Self::LuaEvalSilently(_) ) } } #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum NodeSorter { ByRelativePath, ByIRelativePath, ByExtension, ByIsDir, ByIsFile, ByIsSymlink, ByIsBroken, ByIsReadonly, ByMimeEssence, BySize, ByCreated, ByLastModified, ByCanonicalAbsolutePath, ByICanonicalAbsolutePath, ByCanonicalExtension, ByCanonicalIsDir, ByCanonicalIsFile, ByCanonicalIsReadonly, ByCanonicalMimeEssence, ByCanonicalSize, ByCanonicalCreated, ByCanonicalLastModified, BySymlinkAbsolutePath, ByISymlinkAbsolutePath, BySymlinkExtension, BySymlinkIsDir, BySymlinkIsFile, BySymlinkIsReadonly, BySymlinkMimeEssence, BySymlinkSize, BySymlinkCreated, BySymlinkLastModified, } #[derive(Debug, Clone, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NodeSorterApplicable { pub sorter: NodeSorter, #[serde(default)] pub reverse: bool, } impl PartialEq for NodeSorterApplicable { fn eq(&self, other: &NodeSorterApplicable) -> bool { self.sorter == other.sorter } } impl std::hash::Hash for NodeSorterApplicable { fn hash(&self, state: &mut H) { self.sorter.hash(state); } } impl NodeSorterApplicable { pub fn reversed(mut self) -> Self { self.reverse = !self.reverse; self } fn apply(&self, a: &Node, b: &Node) -> Ordering { let order = match self.sorter { NodeSorter::ByRelativePath => { natord::compare(&a.relative_path, &b.relative_path) } NodeSorter::ByIRelativePath => { natord::compare_ignore_case(&a.relative_path, &b.relative_path) } NodeSorter::ByExtension => a.extension.cmp(&b.extension), NodeSorter::ByIsDir => a.is_dir.cmp(&b.is_dir), NodeSorter::ByIsFile => a.is_file.cmp(&b.is_file), NodeSorter::ByIsSymlink => a.is_symlink.cmp(&b.is_symlink), NodeSorter::ByIsBroken => a.is_broken.cmp(&b.is_broken), NodeSorter::ByIsReadonly => a.is_readonly.cmp(&b.is_readonly), NodeSorter::ByMimeEssence => a.mime_essence.cmp(&b.mime_essence), NodeSorter::BySize => a.size.cmp(&b.size), NodeSorter::ByCreated => a.created.cmp(&b.created), NodeSorter::ByLastModified => a.last_modified.cmp(&b.last_modified), NodeSorter::ByCanonicalAbsolutePath => natord::compare( &a.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), &b.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), NodeSorter::ByICanonicalAbsolutePath => natord::compare_ignore_case( &a.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), &b.canonical .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), NodeSorter::ByCanonicalExtension => a .canonical .as_ref() .map(|s| &s.extension) .cmp(&b.canonical.as_ref().map(|s| &s.extension)), NodeSorter::ByCanonicalIsDir => a .canonical .as_ref() .map(|s| &s.is_dir) .cmp(&b.canonical.as_ref().map(|s| &s.is_dir)), NodeSorter::ByCanonicalIsFile => a .canonical .as_ref() .map(|s| &s.is_file) .cmp(&b.canonical.as_ref().map(|s| &s.is_file)), NodeSorter::ByCanonicalIsReadonly => a .canonical .as_ref() .map(|s| &s.is_readonly) .cmp(&b.canonical.as_ref().map(|s| &s.is_readonly)), NodeSorter::ByCanonicalMimeEssence => a .canonical .as_ref() .map(|s| &s.mime_essence) .cmp(&b.canonical.as_ref().map(|s| &s.mime_essence)), NodeSorter::ByCanonicalSize => a .canonical .as_ref() .map(|s| &s.size) .cmp(&b.canonical.as_ref().map(|s| &s.size)), NodeSorter::ByCanonicalCreated => a .canonical .as_ref() .map(|s| &s.created) .cmp(&b.canonical.as_ref().map(|s| &s.created)), NodeSorter::ByCanonicalLastModified => a .canonical .as_ref() .map(|s| &s.last_modified) .cmp(&b.canonical.as_ref().map(|s| &s.last_modified)), NodeSorter::BySymlinkAbsolutePath => natord::compare( &a.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), &b.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), NodeSorter::ByISymlinkAbsolutePath => natord::compare_ignore_case( &a.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), &b.symlink .as_ref() .map(|s| s.absolute_path.clone()) .unwrap_or_default(), ), NodeSorter::BySymlinkExtension => a .symlink .as_ref() .map(|s| &s.extension) .cmp(&b.symlink.as_ref().map(|s| &s.extension)), NodeSorter::BySymlinkIsDir => a .symlink .as_ref() .map(|s| &s.is_dir) .cmp(&b.symlink.as_ref().map(|s| &s.is_dir)), NodeSorter::BySymlinkIsFile => a .symlink .as_ref() .map(|s| &s.is_file) .cmp(&b.symlink.as_ref().map(|s| &s.is_file)), NodeSorter::BySymlinkIsReadonly => a .symlink .as_ref() .map(|s| &s.is_readonly) .cmp(&b.symlink.as_ref().map(|s| &s.is_readonly)), NodeSorter::BySymlinkMimeEssence => a .symlink .as_ref() .map(|s| &s.mime_essence) .cmp(&b.symlink.as_ref().map(|s| &s.mime_essence)), NodeSorter::BySymlinkSize => a .symlink .as_ref() .map(|s| &s.size) .cmp(&b.symlink.as_ref().map(|s| &s.size)), NodeSorter::BySymlinkCreated => a .symlink .as_ref() .map(|s| &s.created) .cmp(&b.symlink.as_ref().map(|s| &s.created)), NodeSorter::BySymlinkLastModified => a .symlink .as_ref() .map(|s| &s.last_modified) .cmp(&b.symlink.as_ref().map(|s| &s.last_modified)), }; if self.reverse { order.reverse() } else { order } } } #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub enum NodeFilter { RelativePathIs, RelativePathIsNot, IRelativePathIs, IRelativePathIsNot, RelativePathDoesStartWith, RelativePathDoesNotStartWith, IRelativePathDoesStartWith, IRelativePathDoesNotStartWith, RelativePathDoesContain, RelativePathDoesNotContain, IRelativePathDoesContain, IRelativePathDoesNotContain, RelativePathDoesEndWith, RelativePathDoesNotEndWith, IRelativePathDoesEndWith, IRelativePathDoesNotEndWith, RelativePathDoesMatchRegex, RelativePathDoesNotMatchRegex, IRelativePathDoesMatchRegex, IRelativePathDoesNotMatchRegex, AbsolutePathIs, AbsolutePathIsNot, IAbsolutePathIs, IAbsolutePathIsNot, AbsolutePathDoesStartWith, AbsolutePathDoesNotStartWith, IAbsolutePathDoesStartWith, IAbsolutePathDoesNotStartWith, AbsolutePathDoesContain, AbsolutePathDoesNotContain, IAbsolutePathDoesContain, IAbsolutePathDoesNotContain, AbsolutePathDoesEndWith, AbsolutePathDoesNotEndWith, IAbsolutePathDoesEndWith, IAbsolutePathDoesNotEndWith, AbsolutePathDoesMatchRegex, AbsolutePathDoesNotMatchRegex, IAbsolutePathDoesMatchRegex, IAbsolutePathDoesNotMatchRegex, } impl NodeFilter { fn apply(&self, node: &Node, input: &str, regex: Option<&Regex>) -> bool { match self { Self::RelativePathIs => node.relative_path.eq(input), Self::IRelativePathIs => node.relative_path.eq_ignore_ascii_case(input), Self::RelativePathIsNot => !node.relative_path.eq(input), Self::IRelativePathIsNot => !node.relative_path.eq_ignore_ascii_case(input), Self::RelativePathDoesStartWith => node.relative_path.starts_with(input), Self::IRelativePathDoesStartWith => node .relative_path .to_lowercase() .starts_with(&input.to_lowercase()), Self::RelativePathDoesNotStartWith => !node.relative_path.starts_with(input), Self::IRelativePathDoesNotStartWith => !node .relative_path .to_lowercase() .starts_with(&input.to_lowercase()), Self::RelativePathDoesContain => node.relative_path.contains(input), Self::IRelativePathDoesContain => node .relative_path .to_lowercase() .contains(&input.to_lowercase()), Self::RelativePathDoesNotContain => !node.relative_path.contains(input), Self::IRelativePathDoesNotContain => !node .relative_path .to_lowercase() .contains(&input.to_lowercase()), Self::RelativePathDoesEndWith => node.relative_path.ends_with(input), Self::IRelativePathDoesEndWith => node .relative_path .to_lowercase() .ends_with(&input.to_lowercase()), Self::RelativePathDoesNotEndWith => !node.relative_path.ends_with(input), Self::IRelativePathDoesNotEndWith => !node .relative_path .to_lowercase() .ends_with(&input.to_lowercase()), Self::RelativePathDoesMatchRegex => regex .map(|r| r.is_match(&node.relative_path)) .unwrap_or(false), Self::IRelativePathDoesMatchRegex => regex .map(|r| r.is_match(&node.relative_path.to_lowercase())) .unwrap_or(false), Self::RelativePathDoesNotMatchRegex => !regex .map(|r| r.is_match(&node.relative_path)) .unwrap_or(false), Self::IRelativePathDoesNotMatchRegex => !regex .map(|r| r.is_match(&node.relative_path.to_lowercase())) .unwrap_or(false), Self::AbsolutePathIs => node.absolute_path.eq(input), Self::IAbsolutePathIs => node.absolute_path.eq_ignore_ascii_case(input), Self::AbsolutePathIsNot => !node.absolute_path.eq(input), Self::IAbsolutePathIsNot => !node.absolute_path.eq_ignore_ascii_case(input), Self::AbsolutePathDoesStartWith => node.absolute_path.starts_with(input), Self::IAbsolutePathDoesStartWith => node .absolute_path .to_lowercase() .starts_with(&input.to_lowercase()), Self::AbsolutePathDoesNotStartWith => !node.absolute_path.starts_with(input), Self::IAbsolutePathDoesNotStartWith => !node .absolute_path .to_lowercase() .starts_with(&input.to_lowercase()), Self::AbsolutePathDoesContain => node.absolute_path.contains(input), Self::IAbsolutePathDoesContain => node .absolute_path .to_lowercase() .contains(&input.to_lowercase()), Self::AbsolutePathDoesNotContain => !node.absolute_path.contains(input), Self::IAbsolutePathDoesNotContain => !node .absolute_path .to_lowercase() .contains(&input.to_lowercase()), Self::AbsolutePathDoesEndWith => node.absolute_path.ends_with(input), Self::IAbsolutePathDoesEndWith => node .absolute_path .to_lowercase() .ends_with(&input.to_lowercase()), Self::AbsolutePathDoesNotEndWith => !node.absolute_path.ends_with(input), Self::IAbsolutePathDoesNotEndWith => !node .absolute_path .to_lowercase() .ends_with(&input.to_lowercase()), Self::AbsolutePathDoesMatchRegex => regex .map(|r| r.is_match(&node.absolute_path)) .unwrap_or(false), Self::IAbsolutePathDoesMatchRegex => regex .map(|r| r.is_match(&node.absolute_path.to_lowercase())) .unwrap_or(false), Self::AbsolutePathDoesNotMatchRegex => !regex .map(|r| r.is_match(&node.absolute_path)) .unwrap_or(false), Self::IAbsolutePathDoesNotMatchRegex => !regex .map(|r| r.is_match(&node.absolute_path.to_lowercase())) .unwrap_or(false), } } } // Regex doesn't implement Hash and Eq. But we need them. #[derive(Debug, Clone)] pub struct CmpRegex(Regex); impl std::hash::Hash for CmpRegex { fn hash(&self, state: &mut H) { self.0.as_str().hash(state) } } impl PartialEq for CmpRegex { fn eq(&self, other: &Self) -> bool { self.0.as_str() == other.0.as_str() } } impl Eq for CmpRegex {} #[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct NodeFilterApplicable { pub filter: NodeFilter, pub input: String, #[serde(skip)] pub regex: Option, } impl NodeFilterApplicable { pub fn new(filter: NodeFilter, input: String) -> Self { use NodeFilter::*; let regex = if matches!( filter, RelativePathDoesMatchRegex | RelativePathDoesNotMatchRegex | AbsolutePathDoesMatchRegex | AbsolutePathDoesNotMatchRegex ) { Regex::new(&input).ok().map(CmpRegex) } else if matches!( filter, IRelativePathDoesMatchRegex | IRelativePathDoesNotMatchRegex | IAbsolutePathDoesMatchRegex | IAbsolutePathDoesNotMatchRegex ) { Regex::new(&input.to_lowercase()).ok().map(CmpRegex) } else { None }; Self { filter, input, regex, } } fn apply(&self, node: &Node) -> bool { self.filter .apply(node, &self.input, self.regex.as_ref().map(|r| &r.0)) } } #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct ExplorerConfig { pub filters: IndexSet, pub sorters: IndexSet, } impl ExplorerConfig { pub fn filter(&self, node: &Node) -> bool { self.filters.iter().all(|f| f.apply(node)) } pub fn sort(&self, a: &Node, b: &Node) -> Ordering { let mut ord = Ordering::Equal; for s in self.sorters.iter() { ord = ord.then(s.apply(a, b)); } ord } /// Get a reference to the explorer config's filters. pub fn filters(&self) -> &IndexSet { &self.filters } /// Get a reference to the explorer config's sorters. pub fn sorters(&self) -> &IndexSet { &self.sorters } } #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Command { pub command: String, #[serde(default)] pub args: Vec, }