use crate::app::Node; use crate::input::InputOperation; use crate::search::PathItem; use crate::search::SearchAlgorithm; use indexmap::IndexSet; use regex::Regex; use serde::{Deserialize, Serialize}; use std::{cmp::Ordering, sync::Arc}; #[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 next selected node. /// /// Example: /// /// - Lua: `"FocusNextSelection"` /// - YAML: `FocusNextSelection` FocusNextSelection, /// 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 previous selection item. /// /// Example: /// /// - Lua: `"FocusPreviousSelection"` /// - YAML: `FocusPreviousSelection` FocusPreviousSelection, /// 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, /// 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), /// Scroll up by terminal height. /// /// Example: /// /// - Lua: `"ScrollUp"` /// - YAML: `ScrollUp` ScrollUp, /// Scroll down by terminal height. /// /// Example: /// /// - Lua: `"ScrollDown"` /// - YAML: `ScrollDown` ScrollDown, /// Scroll up by half of terminal height. /// /// Example: /// /// - Lua: `"ScrollUpHalf"` /// - YAML: `ScrollUpHalf` ScrollUpHalf, /// Scroll down by half of terminal height. /// /// Example: /// /// - Lua: `"ScrollDownHalf"` /// - YAML: `ScrollDownHalf` ScrollDownHalf, /// 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, /// Go to the previous deep level branch. /// /// Example: /// /// - Lua: `"PreviousVisitedDeepBranch"` /// - YAML: `PreviousVisitedDeepBranch` PreviousVisitedDeepBranch, /// Go to the next deep level branch. /// /// Example: /// /// - Lua: `"NextVisitedDeepBranch"` /// - YAML: `NextVisitedDeepBranch` NextVisitedDeepBranch, /// Follow the symlink under focus to its actual location. /// /// Example: /// /// - Lua: `"FollowSymlink"` /// - YAML: `FollowSymlink` FollowSymlink, /// ### Virtual Root ------------------------------------------------------ /// Sets the virtual root for isolating xplr navigation, similar to /// `--vroot`, but temporary (can be reset back to initial value). /// If the $PWD is outside the vroot, xplr will automatically enter vroot. /// /// Type: { SetVroot = "string" } /// /// Example: /// /// - Lua: `{ SetVroot = "/tmp" }` /// - YAML: `SetVroot: /tmp` SetVroot(String), /// Unset the virtual root temporarily (can be reset back to the initial /// value). /// /// Example: /// /// - Lua: `"UnsetVroot"` /// - YAML: `UnsetVroot` UnsetVroot, /// Toggle virtual root between unset, initial value and $PWD. /// /// Example: /// /// - Lua: `"ToggleVroot"` /// - YAML: `ToggleVroot` ToggleVroot, /// Reset the virtual root back to the initial value. /// /// Example: /// /// - Lua: `"ResetVroot"` /// - YAML: `ResetVroot` ResetVroot, /// ### 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 Operation](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 character 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 /// > `SwitchModeBuiltin` or `SwitchModeCustom` 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 ------------------------------------------------ /// Like `Call0`, but it uses `\n` as the delimiter in input/output pipes, /// hence it cannot handle files with `\n` in the name. /// You may want to use `Call0` instead. Call(Command), /// 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` depending on the expectation. /// /// Type: { Call0 = { command = "string", args = { "list", "of", "string" } } /// /// Example: /// /// - Lua: `{ Call0 = { command = "bash", args = { "-c", "read -p test" } } }` /// - YAML: `Call0: { command: bash, args: ["-c", "read -p test"] }` Call0(Command), /// Like `CallSilently0`, but it uses `\n` as the delimiter in input/output /// pipes, hence it cannot handle files with `\n` in the name. /// You may want to use `CallSilently0` instead. CallSilently(Command), /// Like `Call0` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { CallSilently0 = { command = "string", args = {"list", "of", "string"} } } /// /// Example: /// /// - Lua: `{ CallSilently0 = { command = "tput", args = { "bell" } } }` /// - YAML: `CallSilently0: { command: tput, args: ["bell"] }` CallSilently0(Command), /// Like `BashExec0`, but it uses `\n` as the delimiter in input/output /// pipes, hence it cannot handle files with `\n` in the name. /// You may want to use `BashExec0` instead. BashExec(String), /// An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}` /// where `{string}` is the given value. /// /// Type: { BashExec0 = "string" } /// /// Example: /// /// - Lua: `{ BashExec0 = "read -p test" }` /// - YAML: `BashExec0: "read -p test"` BashExec0(String), /// Like `BashExecSilently0`, but it uses `\n` as the delimiter in /// input/output pipes, hence it cannot handle files with `\n` in the name. /// You may want to use `BashExecSilently0` instead. BashExecSilently(String), /// Like `BashExec0` but without the flicker. The stdin, stdout /// stderr will be piped to null. So it's non-interactive. /// /// Type: { BashExecSilently0 = "string" } /// /// Example: /// /// - Lua: `{ BashExecSilently0 = "tput bell" }` /// - YAML: `BashExecSilently0: "tput bell"` BashExecSilently0(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. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// Filters get automatically cleared when changing 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). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"RemoveLastNodeFilter"` /// - YAML: `RemoveLastNodeFilter` RemoveLastNodeFilter, /// Reset the node [filters](https://xplr.dev/en/filtering) back to the /// default configuration. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ResetNodeFilters"` /// - YAML: `ResetNodeFilters` ResetNodeFilters, /// Clear all the node [filters](https://xplr.dev/en/filtering). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ClearNodeFilters"` /// - YAML: `ClearNodeFilters` ClearNodeFilters, /// ### Sort Operations ---------------------------------------------------- /// Add a [sorter](https://xplr.dev/en/sorting#sorter) to sort nodes while /// exploring directories. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// 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). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ReverseNodeSorters"` /// - YAML: `ReverseNodeSorters` ReverseNodeSorters, /// Remove the last node [sorter](https://xplr.dev/en/sorting#sorter). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"RemoveLastNodeSorter"` /// - YAML: `RemoveLastNodeSorter` RemoveLastNodeSorter, /// Reset the node [sorters](https://xplr.dev/en/sorting#sorter) back to /// the default configuration. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ResetNodeSorters"` /// - YAML: `ResetNodeSorters` ResetNodeSorters, /// Clear all the node [sorters](https://xplr.dev/en/sorting#sorter). /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ClearNodeSorters"` /// - YAML: `ClearNodeSorters` ClearNodeSorters, /// ### Search Operations -------------------------------------------------- /// Search files using the current or default (fuzzy) search algorithm. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Type: { Search = "string" } /// /// Example: /// /// - Lua: `{ Search = "pattern" }` /// - YAML: `Search: pattern` Search(String), /// Calls `Search` with the input taken from the input buffer. /// /// Example: /// /// - Lua: `"SearchFromInput"` /// - YAML: `SearchFromInput` SearchFromInput, /// Search files using fuzzy match algorithm. /// It keeps the filters, but overrides the sorters. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Type: { SearchFuzzy = "string" } /// /// Example: /// /// - Lua: `{ SearchFuzzy = "pattern" }` /// - YAML: `SearchFuzzy: pattern` SearchFuzzy(String), /// Calls `SearchFuzzy` with the input taken from the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Example: /// /// - Lua: `"SearchFuzzyFromInput"` /// - YAML: `SearchFuzzyFromInput` SearchFuzzyFromInput, /// Like `SearchFuzzy`, but doesn't not perform rank based sorting. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Type: { SearchFuzzyUnordered = "string" } /// /// Example: /// /// - Lua: `{ SearchFuzzyUnordered = "pattern" }` /// - YAML: `SearchFuzzyUnordered: pattern` SearchFuzzyUnordered(String), /// Calls `SearchFuzzyUnordered` with the input taken from the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Example: /// /// - Lua: `"SearchFuzzyUnorderedFromInput"` /// - YAML: `SearchFuzzyUnorderedFromInput` SearchFuzzyUnorderedFromInput, /// Search files using regex match algorithm. /// It keeps the filters, but overrides the sorters. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Type: { SearchRegex = "string" } /// /// Example: /// /// - Lua: `{ SearchRegex = "pattern" }` /// - YAML: `SearchRegex: pattern` SearchRegex(String), /// Calls `SearchRegex` with the input taken from the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Example: /// /// - Lua: `"SearchRegexFromInput"` /// - YAML: `SearchRegexFromInput` SearchRegexFromInput, /// Like `SearchRegex`, but doesn't not perform rank based sorting. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Type: { SearchRegexUnordered = "string" } /// /// Example: /// /// - Lua: `{ SearchRegexUnordered = "pattern" }` /// - YAML: `SearchRegexUnordered: pattern` SearchRegexUnordered(String), /// Calls `SearchRegexUnordered` with the input taken from the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// It gets reset automatically when changing directory. /// /// Example: /// /// - Lua: `"SearchRegexUnorderedFromInput"` /// - YAML: `SearchRegexUnorderedFromInput` SearchRegexUnorderedFromInput, /// Toggles between different search algorithms, without changing the input /// buffer /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"ToggleSearchAlgorithm"` /// - YAML: `ToggleSearchAlgorithm` ToggleSearchAlgorithm, /// Enables ranked search without changing the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"EnableOrderedSearch"` /// - YAML: `EnableSearchOrder` EnableSearchOrder, /// Disabled ranked search without changing the input buffer. /// You need to call `ExplorePwd` or `ExplorePwdAsync` explicitly. /// /// Example: /// /// - Lua: `"DisableSearchOrder"` /// - YAML: `DisableSearchOrder` DisableSearchOrder, /// Toggles ranked search without changing the input buffer. /// /// Example: /// /// - Lua: `"ToggleSearchOrder"` /// - YAML: `ToggleSearchOrder` ToggleSearchOrder, /// Accepts the search by keeping the latest focus while in search mode. /// Automatically calls `ExplorePwd`. /// /// Example: /// /// - Lua: `"AcceptSearch"` /// - YAML: `AcceptSearch` AcceptSearch, /// Cancels the search by discarding the latest focus and recovering /// the focus before search. /// Automatically calls `ExplorePwd`. /// /// Example: /// /// - Lua: `"CancelSearch"` /// - YAML: `CancelSearch` CancelSearch, /// ### 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 between {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::Call0(_) | Self::CallSilently(_) | Self::CallSilently0(_) | Self::BashExec(_) | Self::BashExec0(_) | Self::BashExecSilently(_) | Self::BashExecSilently0(_) | 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 {} // See https://github.com/sayanarijit/xplr/issues/503 #[derive(Debug, Deserialize)] #[serde(deny_unknown_fields)] struct NodeFilterApplicableDeserializer { pub filter: NodeFilter, pub input: String, } #[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields, from = "NodeFilterApplicableDeserializer")] pub struct NodeFilterApplicable { pub filter: NodeFilter, pub input: String, #[serde(skip)] pub regex: Option, } impl From for NodeFilterApplicable { fn from(f: NodeFilterApplicableDeserializer) -> Self { NodeFilterApplicable::new(f.filter, f.input) } } 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, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct NodeSearcherApplicable { pub pattern: String, #[serde(default)] pub recoverable_focus: Option, #[serde(default)] pub algorithm: SearchAlgorithm, #[serde(default)] pub unordered: bool, } impl NodeSearcherApplicable { pub fn new( pattern: String, recoverable_focus: Option, algorithm: SearchAlgorithm, unordered: bool, ) -> Self { Self { pattern, recoverable_focus, algorithm, unordered, } } pub fn search(&self, nodes: I) -> Vec where I: IntoIterator, { let engine = self.algorithm.engine(&self.pattern); let ranked_nodes = nodes.into_iter().filter_map(|n| { let item = Arc::new(PathItem::from(n.relative_path.clone())); engine.match_item(item).map(|res| (n, res.rank)) }); if self.unordered { ranked_nodes.map(|(n, _)| n).collect() } else { let mut ranked_nodes = ranked_nodes.collect::>(); ranked_nodes.sort_by(|(_, s1), (_, s2)| s1.cmp(s2)); ranked_nodes.into_iter().map(|(n, _)| n).collect() } } pub fn enable_search_order(self) -> Self { Self { unordered: false, ..self } } pub fn disable_search_order(self) -> Self { Self { unordered: true, ..self } } pub fn toggle_search_order(self) -> Self { Self { unordered: !self.unordered, ..self } } pub fn toggle_algorithm(self) -> Self { Self { algorithm: self.algorithm.toggle(), ..self } } } #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct ExplorerConfig { #[serde(default)] pub filters: IndexSet, #[serde(default)] pub sorters: IndexSet, #[serde(default)] pub searcher: Option, } 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, }