From 3d81a49cecac32d6e796aeb7d28d0fac44a7c965 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Sun, 27 Feb 2022 18:46:53 +0530 Subject: [PATCH] Auto generate messages docs - Huge refactor. - Run `python docs/script/generate.py` to generate `docs/en/src/messages.md`. --- .gitignore | 2 + docs/en/src/SUMMARY.md | 4 + docs/en/src/configuration.md | 4 +- docs/en/src/general-config.md | 4 +- docs/en/src/input-operation.md | 25 + docs/en/src/layouts.md | 4 +- docs/en/src/message.md | 838 +------------------ docs/en/src/messages.md | 1176 ++++++++++++++++++++++++++ docs/en/src/modes.md | 4 +- docs/en/src/node_types.md | 4 +- docs/script/generate.lua | 45 - docs/script/generate.py | 116 +++ src/app.rs | 1156 +------------------------- src/directory_buffer.rs | 26 + src/lib.rs | 4 + src/msg/in_/external.rs | 1409 ++++++++++++++++++++++++++++++++ src/msg/in_/internal.rs | 10 + src/msg/in_/mod.rs | 12 + src/msg/mod.rs | 2 + src/msg/out/mod.rs | 31 + src/node.rs | 159 ++++ src/pipe.rs | 55 ++ 22 files changed, 3060 insertions(+), 2030 deletions(-) create mode 100644 docs/en/src/input-operation.md create mode 100644 docs/en/src/messages.md delete mode 100644 docs/script/generate.lua create mode 100644 docs/script/generate.py create mode 100644 src/directory_buffer.rs create mode 100644 src/msg/in_/external.rs create mode 100644 src/msg/in_/internal.rs create mode 100644 src/msg/in_/mod.rs create mode 100644 src/msg/mod.rs create mode 100644 src/msg/out/mod.rs create mode 100644 src/node.rs create mode 100644 src/pipe.rs diff --git a/.gitignore b/.gitignore index aaccfaa..01773a9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ book/ # Jetbrains config .idea/ + +.venv/ diff --git a/docs/en/src/SUMMARY.md b/docs/en/src/SUMMARY.md index 2ee3b3d..07eda13 100644 --- a/docs/en/src/SUMMARY.md +++ b/docs/en/src/SUMMARY.md @@ -18,6 +18,8 @@ - [Layout][34] - [Mode][35] - [Message][8] + - [Full List of Messages][38] + - [Input Operation][39] - [Borders][31] - [Style][11] - [Sorting][12] @@ -75,3 +77,5 @@ [35]: mode.md [36]: lua-function-calls.md [37]: environment-variables-and-pipes.md +[38]: messages.md +[39]: input-operation.md diff --git a/docs/en/src/configuration.md b/docs/en/src/configuration.md index d45ad10..baf99c0 100644 --- a/docs/en/src/configuration.md +++ b/docs/en/src/configuration.md @@ -18,8 +18,8 @@ The loaded config can be further extended using the `-C` or `--extra-config` command-line option. > **NEED HELP:** Auto generate rest of the docs from [src/init.lua][1] -> using [docs/script/generate.lua][2]. +> using [docs/script/generate.py][2]. [1]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua -[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.lua +[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.py [3]: https://www.lua.org diff --git a/docs/en/src/general-config.md b/docs/en/src/general-config.md index 04c14da..3dfa8eb 100644 --- a/docs/en/src/general-config.md +++ b/docs/en/src/general-config.md @@ -5,7 +5,7 @@ This configuration is exposed via the `xplr.config.general` API. For now, kindly refer to [**init.lua**][1] > **NEED HELP:** Auto generate rest of the docs from [src/init.lua][1] -> using [docs/script/generate.lua][2]. +> using [docs/script/generate.py][2]. [1]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua -[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.lua +[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.py diff --git a/docs/en/src/input-operation.md b/docs/en/src/input-operation.md new file mode 100644 index 0000000..b3a1161 --- /dev/null +++ b/docs/en/src/input-operation.md @@ -0,0 +1,25 @@ +# Input Operation + +Cursor based input operation can be one of the following: + +- { SetCursor = int } +- { InsertCharacter = str } +- "GoToPreviousCharacter" +- "GoToNextCharacter" +- "GoToPreviousWord" +- "GoToNextWord" +- "GoToStart" +- "GoToEnd" +- "DeletePreviousCharacter" +- "DeleteNextCharacter" +- "DeletePreviousWord" +- "DeleteNextWord" +- "DeleteLine" + +## Also See: + +- [Message][1] +- [Full List of Messages][2] + +[1]: message.md +[2]: messages.md diff --git a/docs/en/src/layouts.md b/docs/en/src/layouts.md index 17549e3..a615513 100644 --- a/docs/en/src/layouts.md +++ b/docs/en/src/layouts.md @@ -5,7 +5,7 @@ This configuration is exposed via the `xplr.config.layouts` API. For now, kindly refer to [**init.lua**][1] > **NEED HELP:** Auto generate rest of the docs from [src/init.lua][1] -> using [docs/script/generate.lua][2]. +> using [docs/script/generate.py][2]. [1]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua -[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.lua +[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.py diff --git a/docs/en/src/message.md b/docs/en/src/message.md index bec3346..298fffb 100644 --- a/docs/en/src/message.md +++ b/docs/en/src/message.md @@ -11,7 +11,7 @@ You can send these messages to an xplr session in the following ways: - Via shell command using the [input pipe][4] - Via socket (coming soon) -## Format +### Format To send messages using the [key bindings][2] or [Lua function calls][3], messages are represented in @@ -29,844 +29,14 @@ represented using - FocusPath: "/path/to/file" - Call: { command: bash, args: ["-c", "read -p test"] } -## Full List of Messages +## Also See: -### "ExplorePwd" +- [Full List of Messages][1] -**YAML:** `ExplorePwd` - -Explore the present working directory and register the filtered nodes. -This operation is expensive. So, try to avoid using it too often. - -### "ExplorePwdAsync" - -**YAML:** `ExplorePwdAsync` - -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. - -### "ExploreParentsAsync" - -**YAML:** `ExploreParentsAsync` - -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. - -### "Refresh" - -**YAML:** `Refresh` - -Refresh the UI. -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. - -### "ClearScreen" - -**YAML:** `ClearScreen` - -Clears the screen. - -### "FocusNext" - -**YAML:** `FocusNext` - -Focus next node. - -### { FocusNextByRelativeIndex = int } - -**YAML:** `FocusNextByRelativeIndex: int` - -Focus on the `n`th node relative to the current focus where `n` is a given value. - -**YAML Example:** `FocusNextByRelativeIndex: 2` - -**Lua Example:** `{ FocusNextByRelativeIndex = 2 }` - -### "FocusNextByRelativeIndexFromInput" - -**YAML:** `FocusNextByRelativeIndexFromInput` - -Focus on the `n`th node relative to the current focus where `n` is read from -the input buffer. - -### "FocusPrevious" - -**YAML:** `FocusPrevious` - -Focus on the previous item. - -### { FocusPreviousByRelativeIndex = int } - -**YAML:** `FocusPreviousByRelativeIndex: int` - -Focus on the `-n`th node relative to the current focus where `n` is a given value. - -**YAML Example:** `FocusPreviousByRelativeIndex: 2` - -**Lua Example:** `{ FocusPreviousByRelativeIndex = 2 }` - -### "FocusPreviousByRelativeIndexFromInput" - -**YAML:** `FocusPreviousByRelativeIndexFromInput` - -Focus on the `-n`th node relative to the current focus where `n` is read from -the input buffer. - -### "FocusFirst" - -**YAML:** `FocusFirst` - -Focus on the first node. - -### "FocusLast" - -**YAML:** `FocusLast` - -Focus on the last node. - -### { FocusPath = "string" } - -**YAML:** `FocusPath: string` - -Focus on the given path. - -**YAML Example:** `FocusPath: /path/to/file` - -**Lua Example:** `{ FocusPath = "/path/to/file" }` - -### "FocusPathFromInput" - -**YAML:** `FocusPathFromInput` - -Focus on the path read from input buffer. - -### { FocusByIndex = int } - -**YAML:** `FocusByIndex: int` - -Focus on the absolute `n`th node where `n` is a given value. - -**YAML Example:** `FocusByIndex: 2` - -**Lua Example:** `{ FocusByIndex = 2 }` - -### "FocusByIndexFromInput" - -**YAML:** `FocusByIndexFromInput` - -Focus on the absolute `n`th node where `n` is read from the input buffer. - -### { FocusByFileName = "string" } - -**YAML:** `FocusByFileName: string` - -Focus on the file by name from the present working directory. - -**YAML Example:** `FocusByFileName: filename.ext` - -**Lua Example:** `{ FocusByFileName = "filename.ext" }` - -### { ChangeDirectory = "string" } - -**YAML:** `ChangeDirectory: string` - -Change the present working directory ($PWD) - -**YAML Example:** `ChangeDirectory: /path/to/directory` - -**Lua Example:** `{ ChangeDirectory = "/path/to/directory" }` - -### "Enter" - -**YAML:** `Enter` - -Enter into the currently focused path if it's a directory. - -### "Back" - -**YAML:** `Back` - -Go back to the parent directory. - -### "LastVisitedPath" - -**YAML:** `LastVisitedPath` - -Go to the last path visited. - -### "NextVisitedPath" - -**YAML:** `NextVisitedPath` - -Go to the next path visited. - -### "FollowSymlink" - -**YAML:** `FollowSymlink` - -Follow the symlink under focus to its actual location. - -### { UpdateInputBuffer = [Input Opertaion][71] } - -**YAML:** `BufferInput: Input Operation` - -Update the input buffer using cursor based operations. - -**YAML Example:** `UpdateInputBuffer: GoToPreviousWord` - -**Lua Example:** `{ UpdateInputBuffer = "GoToPreviousWord" }` - -### "UpdateInputBufferFromKey" - -**YAML:** `UpdateInputBufferFromKey` - -Update the input buffer from the key read from keyboard input. - -### { BufferInput = "string" } - -**YAML:** `BufferInput: string` - -Append/buffer the given string into the input buffer. - -**YAML Example:** `BufferInput: foo` - -**Lua Example:** `{ BufferInput = "foo" }` - -### "BufferInputFromKey" - -**YAML:** `BufferInputFromKey` - -Append/buffer the characted read from a keyboard input into the -input buffer. - -### { SetInputBuffer = "string" } - -**YAML:** `SetInputBuffer: string` - -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. - -**YAML Example:** `SetInputBuffer: foo` - -**Lua Example:** `{ SetInputBuffer = "foo" }` - -### "RemoveInputBufferLastCharacter" - -**YAML:** `RemoveInputBufferLastCharacter` - -Remove input buffer's last character. - -### "RemoveInputBufferLastWord" - -**YAML:** `RemoveInputBufferLastWord` - -Remove input buffer's last word. - -### "ResetInputBuffer" - -**YAML:** `ResetInputBuffer` - -Reset the input buffer back to null. It will not show in the UI. - -### { SwitchMode = "string" } - -**YAML:** `SwitchMode: string` - -Switch input [mode][8]. -It clears the input buffer. - -> **NOTE:** To be specific about which mode to switch to, use `SwitchModeBuiltin` or -> `SwitchModeCustom` instead. - -**YAML Example:** `SwitchMode: default` - -**Lua Example:** `{ SwitchMode = "default" }` - -### { SwitchModeKeepingInputBuffer = "string" } - -**YAML:** `SwitchModeKeepingInputBuffer: string` - -Switch input [mode][8]. -It keeps the input buffer. - -> **NOTE:** To be specific about which mode to switch to, use -> `SwitchModeBuiltinKeepingInputBuffer` or -> `SwitchModeCustomKeepingInputBuffer` instead. - -**YAML Example:** `SwitchModeKeepingInputBuffer: default` - -**Lua Example:** `{ SwitchModeKeepingInputBuffer = "default" }` - -### { SwitchModeBuiltin = "string" } - -**YAML:** `SwitchModeBuiltin: string` - -Switch to a [builtin mode][9]. -It clears the input buffer. - -**YAML Example:** `SwitchModeBuiltin: default` - -**Lua Example:** `{ SwitchModeBuiltin = "default" }` - -### { SwitchModeBuiltinKeepingInputBuffer = "string" } - -**YAML:** `SwitchModeBuiltinKeepingInputBuffer: string` - -Switch to a [builtin mode][9]. -It keeps the input buffer. - -**YAML Example:** `SwitchModeBuiltinKeepingInputBuffer: default` - -**Lua Example:** `{ SwitchModeBuiltinKeepingInputBuffer = "default" }` - -### { SwitchModeCustom = "string" } - -**YAML:** `SwitchModeCustom: string` - -Switch to a [custom mode][10]. -It clears the input buffer. - -**YAML Example:** `SwitchModeCustom: my_custom_mode` - -**Lua Example:** `{ SwitchModeCustom = "my_custom_mode" }` - -### { SwitchModeCustomKeepingInputBuffer = "string" } - -**YAML:** `SwitchModeCustom: string` - -Switch to a [custom mode][10]. -It keeps the input buffer. - -**YAML Example:** `SwitchModeCustomKeepingInputBuffer: my_custom_mode` - -**Lua Example:** `{ SwitchModeCustomKeepingInputBuffer = "my_custom_mode" }` - -### "PopMode" - -**YAML:** `PopMode` - -Pop the last mode from the history and switch to it. -It clears the input buffer. - -### PopModeKeepingInputBuffer - -**YAML:** `PopModeKeepingInputBuffer` - -Pop the last mode from the history and switch to it. -It keeps the input buffer. - -### { SwitchLayout = "string" } - -**YAML:** `SwitchLayout: string` - -Switch [layout][11]. - -> **NOTE:** To be specific about which layout to switch to, use `SwitchLayoutBuiltin` or -> `SwitchLayoutCustom` instead. - -**YAML Example:** `SwitchLayout: default` - -**Lua Example:** `{ SwitchLayout = "default" }` - -### { SwitchLayoutBuiltin = "string" } - -**YAML:** `SwitchLayoutBuiltin: string` - -Switch to a [builtin layout][12]. - -**YAML Example:** `SwitchLayoutBuiltin: default` - -**Lua Example:** `{ SwitchLayoutBuiltin = "default" }` - -### { SwitchLayoutCustom = "string" } - -**YAML:** `SwitchLayoutCustom: string` - -Switch to a [custom layout][13]. - -**YAML Example:** `SwitchLayoutCustom: my_custom_layout` - -**Lua Example:** `{ SwitchLayoutCustom = "my_custom_layout" }` - -### { Call = "string" } - -**YAML:** `Call: string` - -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. - -**YAML Example:** `Call: { command: bash, args: ["-c", "read -p test"] }` - -**Lua Example:** `{ Call = { command = "bash", args = { "-c", "read -p test" } } }` - -### { CallSilently = "string" } - -**YAML:** `CallSilently: string` - -Like `Call` but without the flicker. The stdin, stdout -stderr will be piped to null. So it's non-interactive. - -**YAML Example:** `CallSilently: { command: tput, args: ["bell"] }` - -**Lua Example:** `{ CallSilently = { command = "tput", args = { "bell" } } }` - -### { BashExec = "string" } - -**YAML:** `BashExec: string` - -An alias to `Call: {command: bash, args: ["-c", "{string}"], silent: false}` -where `{string}` is the given value. - -**YAML Example:** `BashExec: "read -p test"` - -**Lua Example:** `{ BashExec = "read -p test" }` - -### { BashExecSilently = "string" } - -**YAML:** `BashExecSilently(String)` - -Like `BashExec` but without the flicker. The stdin, stdout -stderr will be piped to null. So it's non-interactive. - -**YAML Example:** `BashExecSilently: "tput bell"` - -**Lua Example:** `{ BashExecSilently = "tput bell" }` - -### { CallLua = "string" } - -**YAML:** `CallLua: string` - -Call a Lua function. - -A [Lua Context][14] object will be passed to the [function][3] as argument. -The function can optionally return a list of messages for xplr to handle -after the executing the function. - -**YAML Example:** `CallLua: custom.some_custom_funtion` - -**Lua Example:** `{ CallLua = "custom.some_custom_funtion" }` - -### { CallLuaSilently = "string" } - -**YAML:** `CallLuaSilently: string` - -Like `CallLua` but without the flicker. The stdin, stdout -stderr will be piped to null. So it's non-interactive. - -**YAML Example:** `CallLuaSilently: custom.some_custom_function` - -**Lua Example:** `{ CallLuaSilently = "custom.some_custom_function" }` - -### { LuaEval = "string" } - -**YAML:** `LuaEval: 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][14] argument. - -**YAML Example:** `LuaEval: "return { { LogInfo = io.read() } }"` -**YAML Example:** `LuaEval: "function(app) return { { LogInfo = app.pwd } } end"` - -**Lua Example:** `{ LuaEval = [[return { { LogInfo = io.read() } }]] }` -**Lua Example:** `{ LuaEval = [[function(app) return { { LogInfo = app.pwd } } end]] }` - -### { LuaEvalSilently = "string" } - -**YAML:** `LuaEvalSilently: string` - -Like `LuaEval` but without the flicker. The stdin, stdout -stderr will be piped to null. So it's non-interactive. - -**YAML Example:** `LuaEvalSilently: "return { { LogInfo = 'foo' } }"` - -**Lua Example:** `{ LuaEvalSilently = [[return { { LogInfo = "foo" } }]] }` - -### "Select" - -**YAML:** `Select` - -Select the focused node. - -### "SelectAll" - -**YAML:** `SelectAll` - -Select all the visible nodes. - -### { SelectPath = "string" } - -**YAML:** `SelectPath: string` - -Select the given path. - -**YAML Example:** `SelectPath: /path/to/file` - -**Lua Example:** `{ SelectPath = "/path/to/file" }` - -### "UnSelect" - -**YAML:** `UnSelect` - -Unselect the focused node. - -### "UnSelectAll" - -**YAML:** `UnSelectAll` - -Unselect all the visible nodes. - -### { UnSelectPath = "string)" } - -**YAML:** `UnSelectPath: string` - -UnSelect the given path. - -**YAML Example:** `UnSelectPath: /path/to/file` - -**Lua Example:** `{ UnSelectPath = "/path/to/file" }` - -### "ToggleSelection" - -**YAML:** `ToggleSelection` - -Toggle selection on the focused node. - -### "ToggleSelectAll" - -**YAML:** `ToggleSelectAll` - -Toggle between select all and unselect all. - -### { ToggleSelectionByPath = "string" } - -**YAML:** `ToggleSelectionByPath: string` - -Toggle selection by file path. - -**YAML Example:** `ToggleSelectionByPath: /path/to/file` - -**Lua Example:** `{ ToggleSelectionByPath = "/path/to/file" }` - -### "ClearSelection" - -**YAML:** `ClearSelection` - -Clear the selection. - -### { AddNodeFilter = { filter = [Filter][15], input = "string" } - -**YAML:** `AddNodeFilter: { filter = Filter, input = string }` - -Add a [filter][16] to exclude nodes while exploring directories. - -**YAML Example:** `AddNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` - -**Lua Example:** `{ AddNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }` - -### { RemoveNodeFilter = { filter = [Filter][15], input = "string" } - -**YAML:** `RemoveNodeFilter: { filter = Filter, input = string` - -Remove an existing [filter][16]. - -**YAML Example:** `RemoveNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` - -**Lua Example:** `{ RemoveNodeFilter: { filter: "RelativePathDoesStartWith", input: "foo" } }` - -### { ToggleNodeFilter = { filter = [Filter][15], input = "string" } - -**YAML:** `ToggleNodeFilter: { filter = Filter, input = string }` - -Remove a [filter][16] if it exists, else, add a it. - -**YAML Example:** `ToggleNodeFilter: { filter: RelativePathDoesStartWith, input: foo }` - -**Lua Example:** `{ ToggleNodeFilter = { filter = "RelativePathDoesStartWith", input = "foo" } }` - -### { AddNodeFilterFromInput = [Filter][15] } - -**YAML:** `AddNodeFilterFromInput: Filter` - -Add a node [filter][16] reading the input from the buffer. - -**YAML Example:** `AddNodeFilterFromInput: RelativePathDoesStartWith` - -**Lua Example:** `{ AddNodeFilterFromInput = "RelativePathDoesStartWith" }` - -### { RemoveNodeFilterFromInput = [Filter][15] } - -**YAML:** `RemoveNodeFilterFromInput: Filter` - -Remove a node [filter][16] reading the input from the buffer. - -**YAML Example:** `RemoveNodeFilterFromInput: RelativePathDoesStartWith` - -**Lua Example:** `{ RemoveNodeFilterFromInput = "RelativePathDoesStartWith" }` - -### "RemoveLastNodeFilter" - -**YAML:** `RemoveLastNodeFilter` - -Remove the last node [filter][16]. - -### "ResetNodeFilters" - -**YAML:** `ResetNodeFilters` - -Reset the node [filters][16] back to the default configuration. - -### "ClearNodeFilters" - -**YAML:** `ClearNodeFilters` - -Clear all the node [filters][16]. - -### { AddNodeSorter = { sorter = [Sorter][17], reverse = bool } } - -**YAML:** `AddNodeSorter: { sorter: Sorter, reverse = bool }` - -Add a [sorter][17] to sort nodes while exploring directories. - -**YAML Example:** `AddNodeSorter: { sorter: ByRelativePath, reverse: false }` - -**YAML Example:** `{ AddNodeSorter = { sorter = "ByRelativePath", reverse = false } }` - -### { RemoveNodeSorter = [Sorter][17] } - -**YAML:** `RemoveNodeSorter: Sorter` - -Remove an existing [sorter][17]. - -**YAML Example:** `RemoveNodeSorter: ByRelativePath` - -**Lua Example:** `{ RemoveNodeSorter = "ByRelativePath" }` - -### { ReverseNodeSorter = [Sorter][17] } - -**YAML:** `ReverseNodeSorter: Sorter` - -Reverse a node [sorter][17]. - -**YAML Example:** `ReverseNodeSorter: ByRelativePath` - -**Lua Example:** `{ ReverseNodeSorter = "ByRelativePath" }` - -### { ToggleNodeSorter = { sorter = [Sorter][17], reverse = bool } } - -**YAML:** `ToggleNodeSorter: { sorter: Sorter, reverse = bool }` - -Remove a [sorter][17] if it exists, else, add a it. - -**YAML Example:** `ToggleSorterSorter: {sorter: ByRelativePath, reverse: false }` - -**Lua Example:** `{ ToggleSorterSorter: { sorter = "ByRelativePath", reverse = false } }` - -### "ReverseNodeSorters" - -**YAML:** `ReverseNodeSorters` - -Reverse the node [sorters][17]. - -### "RemoveLastNodeSorter" - -**YAML:** `RemoveLastNodeSorter` - -Remove the last node [sorter][17]. - -### "ResetNodeSorters" - -**YAML:** `ResetNodeSorters` - -Reset the node [sorters][17] back to the default configuration. - -### "ClearNodeSorters" - -**YAML:** `ClearNodeSorters` - -Clear all the node [sorters][17]. - -### "EnableMouse" - -**YAML:** `EnableMouse` - -Enable mouse - -### "DisableMouse" - -**YAML:** `DisableMouse` - -Disable mouse - -### "ToggleMouse" - -**YAML:** `ToggleMouse` - -Toggle mouse - -### { StartFifo = "string" } - -**YAML:** `StartFifo: string` - -Start piping the focused path to the given fifo path - -**YAML Example:** `StartFifo: /tmp/xplr.fifo` - -**Lua Example:** `{ StartFifo = "/tmp/xplr.fifo }` - -### "StopFifo" - -**YAML:** `StopFifo` - -Close the active fifo and stop piping. - -### { ToggleFifo = "string" } - -**YAML:** `ToggleFifo: string` - -Toggle betwen {Start|Stop}Fifo - -**YAML Example:** `ToggleFifo: /path/to/fifo` - -**Lua Example:** `{ ToggleFifo = "/path/to/fifo" }` - -### { LogInfo = "string" } - -**YAML:** `LogInfo: string` - -Log information message. - -**YAML Example:** `LogInfo: launching satellite` - -**Lua Example:** `{ LogInfo = "launching satellite" }` - -### { LogSuccess = "String" } - -**YAML:** `LogSuccess: string` - -Log a success message. - -**YAML Example:** `LogSuccess: satellite reached destination`. - -**Lua Example:** `{ LogSuccess = "satellite reached destination" }`. - -### { LogWarning = "string" } - -**YAML:** `LogWarning: string` - -Log an warning message. - -**YAML Example:** `LogWarning: satellite is heating` - -**Lua Example:** `{ LogWarning = "satellite is heating" }` - -### { LogError = "string" } - -**YAML:** `LogError: string` - -Log an error message. - -**YAML Example:** `LogError: satellite crashed` - -**Lua Example:** `{ LogError = "satellite crashed" }` - -### "Quit" - -**YAML:** `Quit` - -Quit with returncode zero (success). - -### "PrintPwdAndQuit" - -**YAML:** `PrintPwdAndQuit` - -Print $PWD and quit. - -### "PrintFocusPathAndQuit" - -**YAML:** `PrintFocusPathAndQuit` - -Print the path under focus and quit. It can be empty string if there's nothing to focus. - -### "PrintSelectionAndQuit" - -**YAML:** `PrintSelectionAndQuit` - -Print the selected paths and quit. It can be empty is no path is selected. - -### "PrintResultAndQuit" - -**YAML:** `PrintResultAndQuit` - -Print the selected paths if it's not empty, else, print the focused node's path. - -### "PrintAppStateAndQuit" - -**YAML:** `PrintAppStateAndQuit` - -Print the state of application in YAML format. Helpful for debugging or generating -the default configuration file. - -### { Debug = "string" } - -**YAML:** `Debug: string` - -Write the application state to a file, without quitting. Also helpful for debugging. - -**YAML Example:** `Debug: /path/to/file` - -**Lua Example:** `{ Debug = "/path/to/file" }` - -### "Terminate" - -**YAML:** `Terminate` - -Terminate the application with a non-zero return code. - -## InputOperation - -Cursor based input operation can be one of the following: - -- { SetCursor = int } -- { InsertCharacter = str } -- "GoToPreviousCharacter" -- "GoToNextCharacter" -- "GoToPreviousWord" -- "GoToNextWord" -- "GoToStart" -- "GoToEnd" -- "DeletePreviousCharacter" -- "DeleteNextCharacter" -- "DeletePreviousWord" -- "DeleteNextWord" -- "DeleteLine" - -[1]: #full-list-of-messages +[1]: messages.md [2]: key-bindings.md [3]: lua-function-calls.md [4]: environment-variables-and-pipes.md#input-pipe [5]: https://www.lua.org/ [6]: http://yaml.org/ [7]: https://www.json.org -[8]: modes.md#mode -[9]: modes.md#builtin -[10]: modes.md#custom -[11]: layouts.md -[12]: layouts.md#builtin -[13]: layouts.md#custom -[14]: lua-function-calls.md#lua-context -[15]: filtering.md#filter -[16]: filtering.md -[17]: sorting.md#sorter -[71]: #inputoperation diff --git a/docs/en/src/messages.md b/docs/en/src/messages.md new file mode 100644 index 0000000..696da60 --- /dev/null +++ b/docs/en/src/messages.md @@ -0,0 +1,1176 @@ + +# Full List of Messages + +xplr messages categorized based on their purpose. + +## Categories + +- [Exploring](#exploring) +- [Screen](#screen) +- [Navigation](#navigation) +- [Reading Input](#reading-input) +- [Switching Mode](#switching-mode) +- [Switching Layout](#switching-layout) +- [Executing Commands](#executing-commands) +- [Calling Lua Functions](#calling-lua-functions) +- [Select Operations](#select-operations) +- [Filter Operations](#filter-operations) +- [Sort Operations](#sort-operations) +- [Mouse Operations](#mouse-operations) +- [Fifo Operations](#fifo-operations) +- [Logging](#logging) +- [Debugging](#debugging) +- [Quit Options](#quit-options) + +### Exploring + +#### ExplorePwd + + +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` + +#### ExplorePwdAsync + + +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` + +#### ExploreParentsAsync + + +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` + +### Screen + +#### ClearScreen + + +Clear the screen. + +Example: + +- Lua: "ClearScreen" +- YAML: `ClearScreen` + +#### Refresh + + +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` + +### Navigation + +#### FocusNext + + +Focus next node. + +Example: + +- Lua: "FocusNext" +- YAML: `FocusNext` + +#### FocusNextByRelativeIndex + + +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` + +#### FocusNextByRelativeIndexFromInput + + +Focus on the `n`th node relative to the current focus where `n` is read +from the input buffer. + +Example: + +- Lua: "FocusNextByRelativeIndexFromInput" +- YAML: `FocusNextByRelativeIndexFromInput` + +#### FocusPrevious + + +Focus on the previous item. + +Example: + +- Lua: "FocusPrevious" +- YAML: `FocusPrevious` + +#### FocusPreviousByRelativeIndex + + +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` + +#### FocusPreviousByRelativeIndexFromInput + + +Focus on the `-n`th node relative to the current focus where `n` is +read from the input buffer. + +Example: + +- Lua: "FocusPreviousByRelativeIndexFromInput" +- YAML: `FocusPreviousByRelativeIndexFromInput` + +#### FocusFirst + + +Focus on the first node. + +Example: + +- Lua: "FocusFirst" +- YAML: `FocusFirst` + + +#### FocusLast + + +Focus on the last node. + +Example: +- Lua: "FocusLast" +- YAML: `FocusLast` + +#### FocusPath + + +Focus on the given path. + +Type: { FocusPath = "string" } + +Example: + +- Lua: `{ FocusPath = "/path/to/file" }` +- YAML: `FocusPath: /path/to/file` + +#### FocusPathFromInput + + +Focus on the path read from input buffer. + +Example: + +- Lua: "FocusPathFromInput" +- YAML: `FocusPathFromInput` + +#### FocusByIndex + + +Focus on the absolute `n`th node where `n` is a given value. + +Type: { FocusByIndex = int } + +Example: + +- Lua: { FocusByIndex = 2 } +- YAML: `FocusByIndex: 2` + +#### FocusByIndexFromInput + + +Focus on the absolute `n`th node where `n` is read from the input buffer. + +Example: + +- Lua: "FocusByIndexFromInput" +- YAML: `FocusByIndexFromInput` + +#### FocusByFileName + + + +**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` + +#### ChangeDirectory + + +Change the present working directory ($PWD) + +Type: { ChangeDirectory = "string" } + +Example: + +- Lua: { ChangeDirectory = "/path/to/directory" } +- YAML: `ChangeDirectory: /path/to/directory` + +#### Enter + + +Enter into the currently focused path if it's a directory. + +Example: + +- Lua: "Enter" +- YAML: `Enter` + +#### Back + + +Go back to the parent directory. + +Example: + +- Lua:"Back" +- YAML: `Back` + +#### LastVisitedPath + + +Go to the last path visited. + +Example: + +- Lua: "LastVisitedPath" +- YAML: `LastVisitedPath` + +#### NextVisitedPath + + +Go to the next path visited. + +Example: + +- Lua: "NextVisitedPath" +- YAML: `NextVisitedPath` + +#### FollowSymlink + + + +Follow the symlink under focus to its actual location. + +Example: + +Lua: "FollowSymlink" +YAML: `FollowSymlink` + +### Reading Input + +#### UpdateInputBuffer + + +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` + +#### UpdateInputBufferFromKey + + +Update the input buffer from the key read from keyboard input. + +Example: + +- Lua: "UpdateInputBufferFromKey" +- YAML: `UpdateInputBufferFromKey` + +#### BufferInput + + +Append/buffer the given string into the input buffer. + +Type: { BufferInput = "string" } + +Example: + +- Lua: { BufferInput = "foo" } +- YAML: `BufferInput: foo` + +#### BufferInputFromKey + + +Append/buffer the characted read from a keyboard input into the +input buffer. + +Example: + +- Lua: "BufferInputFromKey" +- YAML: `BufferInputFromKey` + +#### SetInputBuffer + + +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` + +#### RemoveInputBufferLastCharacter + + +Remove input buffer's last character. + +Example: + +- Lua: "RemoveInputBufferLastCharacter" +- YAML: `RemoveInputBufferLastCharacter` + +#### RemoveInputBufferLastWord + + +Remove input buffer's last word. + +Example: + +- Lua: "RemoveInputBufferLastWord" +- YAML: `RemoveInputBufferLastWord` + +#### ResetInputBuffer + + +Reset the input buffer back to null. It will not show in the UI. + +Example: + +- Lua: "ResetInputBuffer" +- YAML: `ResetInputBuffer` + +### Switching Mode + +#### SwitchMode + + +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. + +#### SwitchModeKeepingInputBuffer + + +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. + +#### SwitchModeBuiltin + + +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` + +#### SwitchModeBuiltinKeepingInputBuffer + + +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` + +#### SwitchModeCustom + + +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` + +#### SwitchModeCustomKeepingInputBuffer + + +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` + +#### PopMode + + +Pop the last mode from the history and switch to it. +It clears the input buffer. + +Example: + +- Lua: "PopMode" +- YAML: `PopMode` + +#### PopModeKeepingInputBuffer + + +Pop the last mode from the history and switch to it. +It keeps the input buffer. + +Example: + +- Lua: PopModeKeepingInputBuffer +- YAML: `PopModeKeepingInputBuffer` + +### Switching Layout + +#### SwitchLayout + + +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. + +#### SwitchLayoutBuiltin + + +Switch to a [builtin layout](https://xplr.dev/en/layouts#builtin). + +Type: { SwitchLayoutBuiltin = "string" } + +Example: + +- Lua: { SwitchLayoutBuiltin = "default" } +- YAML: `SwitchLayoutBuiltin: default` + +#### SwitchLayoutCustom + + +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` + +### Executing Commands + +#### Call + + +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"] }` + +#### CallSilently + + +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"] }` + +#### BashExec + + +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"` + +#### BashExecSilently + + +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"` + +### Calling Lua Functions + +#### CallLua + + +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` + +#### CallLuaSilently + + +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` + +#### LuaEval + + +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"` + +#### LuaEvalSilently + + +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' } }"` + +### Select Operations + +#### Select + + +Select the focused node. + +Example: + +- Lua: "Select" +- YAML: `Select` + +#### SelectAll + + +Select all the visible nodes. + +Example: + +- Lua: "SelectAll" +- YAML: `SelectAll` + +#### SelectPath + + +Select the given path. + +Type: { SelectPath = "string" } + +Example: + +- Lua: { SelectPath = "/path/to/file" } +- YAML: `SelectPath: /path/to/file` + +#### UnSelect + + +Unselect the focused node. + +Example: + +- Lua: "UnSelect" +- YAML: `UnSelect` + +#### UnSelectAll + + +Unselect all the visible nodes. + +Example: + +- Lua: "UnSelectAll" +- YAML: `UnSelectAll` + +#### UnSelectPath + + +UnSelect the given path. + +Type: { UnSelectPath = "string)" } + +Example: + +- Lua: { UnSelectPath = "/path/to/file" } +- YAML: `UnSelectPath: /path/to/file` + +#### ToggleSelection + + +Toggle selection on the focused node. + +Example: + +- Lua: "ToggleSelection" +- YAML `ToggleSelection` + +#### ToggleSelectAll + + +Toggle between select all and unselect all. +Example: + +- Lua: "ToggleSelectAll" +- YAML: `ToggleSelectAll` + +#### ToggleSelectionByPath + + +Toggle selection by file path. + +Type: { ToggleSelectionByPath = "string" } + +Example: + +- Lua: { ToggleSelectionByPath = "/path/to/file" } +- YAML: `ToggleSelectionByPath: /path/to/file` + +#### ClearSelection + + +Clear the selection. + +Example: + +- Lua: "ClearSelection" +- YAML: `ClearSelection` + +### Filter Operations + +#### AddNodeFilter + + +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 }` + +#### RemoveNodeFilter + + +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 }` + +#### ToggleNodeFilter + + +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 }` + +#### AddNodeFilterFromInput + + +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` + +#### RemoveNodeFilterFromInput + + +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` + +#### RemoveLastNodeFilter + + +Remove the last node [filter](https://xplr.dev/en/filtering). + +Example: + +- Lua: "RemoveLastNodeFilter" +- YAML: `RemoveLastNodeFilter` + +#### ResetNodeFilters + + +Reset the node [filters](https://xplr.dev/en/filtering) back to the +default configuration. + +Example: + +- Lua: "ResetNodeFilters" +- YAML: `ResetNodeFilters` + +#### ClearNodeFilters + + +Clear all the node [filters](https://xplr.dev/en/filtering). + +Example: + +- Lua: "ClearNodeFilters" +- YAML: `ClearNodeFilters` + +### Sort Operations + +#### AddNodeSorter + + +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 }` + +#### RemoveNodeSorter + + +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` + +#### ReverseNodeSorter + + +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` + +#### ToggleNodeSorter + + +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 }` + +#### ReverseNodeSorters + + +Reverse the node [sorters](https://xplr.dev/en/sorting#sorter). + +Example: + +- Lua: "ReverseNodeSorters" +- YAML: `ReverseNodeSorters` + +#### RemoveLastNodeSorter + + +Remove the last node [sorter](https://xplr.dev/en/sorting#sorter). + +Example: + +- Lua: "RemoveLastNodeSorter" +- YAML: `RemoveLastNodeSorter` + +#### ResetNodeSorters + + +Reset the node [sorters](https://xplr.dev/en/sorting#sorter) back to +the default configuration. + +Example: + +- Lua: "ResetNodeSorters" +- YAML: `ResetNodeSorters` + +#### ClearNodeSorters + + +Clear all the node [sorters](https://xplr.dev/en/sorting#sorter). + +Example: + +- Lua: "ClearNodeSorters" +- YAML: `ClearNodeSorters` + +### Mouse Operations + +#### EnableMouse + + +Enable mouse + +Example: + +- Lua: "EnableMouse" +- YAML: `EnableMouse` + +#### DisableMouse + + +Disable mouse + +Example: + +- Lua: "DisableMouse" +- YAML: `DisableMouse` + +#### ToggleMouse + + +Toggle mouse + +Example: + +- Lua: "ToggleMouse" +- YAML: `ToggleMouse` + +### Fifo Operations + +#### StartFifo + + +Start piping the focused path to the given fifo path + +Type: { StartFifo = "string" } + +Example: + +- Lua: { StartFifo = "/tmp/xplr.fifo } +- YAML: `StartFifo: /tmp/xplr.fifo` + +#### StopFifo + + +Close the active fifo and stop piping. + +Example: + +- Lua: "StopFifo" +- YAML: `StopFifo` + +#### ToggleFifo + + +Toggle betwen {Start|Stop}Fifo + +Type: { ToggleFifo = "string" } + +Example: + +- Lua: { ToggleFifo = "/path/to/fifo" } +- YAML: `ToggleFifo: /path/to/fifo` + +### Logging + +#### LogInfo + + +Log information message. + +Type: { LogInfo = "string" } + +Example: + +- Lua: { LogInfo = "launching satellite" } +- YAML: `LogInfo: launching satellite` + +#### LogSuccess + + +Log a success message. + +Type: { LogSuccess = "String" } + +Example: + +- Lua: { LogSuccess = "satellite reached destination" } +- YAML: `LogSuccess: satellite reached destination` + +#### LogWarning + + +Log an warning message. + +Type: { LogWarning = "string" } + +Example: + +- Lua: { LogWarning = "satellite is heating" } +- YAML: `LogWarning: satellite is heating` + +#### LogError + + +Log an error message. + +Type: { LogError = "string" } + +Example: + +- Lua: { LogError = "satellite crashed" } +- YAML: `LogError: satellite crashed` + +### Debugging + +#### Debug + + +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` + +### Quit Options + +#### Quit + + +Example: + +- Lua: "Quit" +- YAML: `Quit` + +Quit with returncode zero (success). + +#### PrintPwdAndQuit + + +Print $PWD and quit. + +Example: + +- Lua: "PrintPwdAndQuit" +- YAML: `PrintPwdAndQuit` + +#### PrintFocusPathAndQuit + + +Print the path under focus and quit. It can be empty string if there's +nothing to focus. + +Example: + +- Lua: "PrintFocusPathAndQuit" +- YAML: `PrintFocusPathAndQuit` + +#### PrintSelectionAndQuit + + +Print the selected paths and quit. It can be empty is no path is +selected. + +Example: + +- Lua: "PrintSelectionAndQuit" +- YAML: `PrintSelectionAndQuit` + +#### PrintResultAndQuit + + +Print the selected paths if it's not empty, else, print the focused +node's path. + +Example: + +- Lua: "PrintResultAndQuit" +- YAML: `PrintResultAndQuit` + +#### PrintAppStateAndQuit + + +Print the state of application in YAML format. Helpful for debugging or +generating the default configuration file. + +Example: + +- Lua: "PrintAppStateAndQuit" +- YAML: `PrintAppStateAndQuit` + +#### Terminate + + +Terminate the application with a non-zero return code. + +Example: + +- Lua: "Terminate" +- YAML: `Terminate` + + +## Also See: + +- [Message](message.md) + diff --git a/docs/en/src/modes.md b/docs/en/src/modes.md index 46ff115..1c7fe57 100644 --- a/docs/en/src/modes.md +++ b/docs/en/src/modes.md @@ -5,7 +5,7 @@ This configuration is exposed via the `xplr.config.modes` API. For now, kindly refer to [**init.lua**][1] > **NEED HELP:** Auto generate rest of the docs from [src/init.lua][1] -> using [docs/script/generate.lua][2]. +> using [docs/script/generate.py][2]. [1]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua -[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.lua +[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.py diff --git a/docs/en/src/node_types.md b/docs/en/src/node_types.md index 83f9fe7..1cd16d2 100644 --- a/docs/en/src/node_types.md +++ b/docs/en/src/node_types.md @@ -5,7 +5,7 @@ This configuration is exposed via the `xplr.config.node_types` API. For now, kindly refer to [**init.lua**][1] > **NEED HELP:** Auto generate rest of the docs from [src/init.lua][1] -> using [docs/script/generate.lua][2]. +> using [docs/script/generate.py][2]. [1]: https://github.com/sayanarijit/xplr/blob/main/src/init.lua -[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.lua +[2]: https://github.com/sayanarijit/xplr/blob/main/docs/script/generate.py diff --git a/docs/script/generate.lua b/docs/script/generate.lua deleted file mode 100644 index b3022e8..0000000 --- a/docs/script/generate.lua +++ /dev/null @@ -1,45 +0,0 @@ --- Configuration documentation generator --- --- TODO implement --- --- This script generates configuration docs by parsing the comments and --- properties from `src/init.lua`. --- --- Usage: --- --- ``` --- lua ./docs/script/generate.lua --- ``` --- --- Or --- --- ``` --- xplr -C ./docs/script/generate.lua --on-load Quit --- ``` - --- Generates ./docs/en/src/configuration.md -local function generate_configuration(lines) end - --- Generates ./docs/en/src/general-config.md -local function generate_general_config(lines) end - --- Generates ./docs/en/src/node_types.md -local function generate_node_types(lines) end - --- Generates ./docs/en/src/general-config.md -local function generate_layouts(lines) end - --- Generates ./docs/en/src/general-config.md -local function generate_modes(lines) end - -local function main() - local init = io.lines("./src/init.lua") - - generate_configuration(init) - generate_general_config(init) - generate_node_types(init) - generate_layouts(init) - generate_modes(init) -end - -main() diff --git a/docs/script/generate.py b/docs/script/generate.py new file mode 100644 index 0000000..c89e85c --- /dev/null +++ b/docs/script/generate.py @@ -0,0 +1,116 @@ +from dataclasses import dataclass + + +@dataclass +class Section: + title: str + body: list + + +@dataclass +class Category: + title: str + sections: list + + +@dataclass +class Result: + categories: list + msgs: list + + +TEMPLATE = """ +# Full List of Messages + +xplr messages categorized based on their purpose. + +## Categories + +{categories} + +{msgs} + +## Also See: + +- [Message](message.md) +""" + + +def gen_messages(): + path = "./src/msg/in_/external.rs" + res = [] + reading = False + + with open(path) as f: + lines = iter(f.read().splitlines()) + + for line in lines: + line = line.strip() + + if line.startswith("pub enum ExternalMsg {"): + reading = True + continue + + if not reading: + continue + + if line == "}": + break + + if line.startswith("/// ### "): + line = line.lstrip("/// ### ").rstrip("-").strip() + sec = Section(title=None, body=[]) + cat = Category(title=line, sections=[sec]) + res.append(cat) + continue + + if line.startswith("/// "): + line = line.lstrip("/// ").strip() + res[-1].sections[-1].body.append(line) + continue + + elif not line.strip() or line.strip() == "///": + res[-1].sections[-1].body.append("") + + elif "," in line: + line = line.split(",")[0].split("(")[0] + res[-1].sections[-1].title = line + + sec = Section(title=None, body=[]) + res[-1].sections.append(sec) + continue + + result = Result(categories=[], msgs=[]) + + for cat in res: + slug = cat.title.lower().replace(" ", "-") + result.categories.append(f"- [{cat.title}](#{slug})") + result.msgs.append(f"### {cat.title}") + result.msgs.append("") + + for sec in cat.sections: + if not sec.title: + continue + + result.msgs.append(f"#### {sec.title}") + result.msgs.append("") + for line in sec.body: + result.msgs.append(f"{line}") + result.msgs.append("") + + return result + + +def main(): + res = gen_messages() + doc = TEMPLATE.format( + categories="\n".join(res.categories), msgs="\n".join(res.msgs) + ) + + print(doc) + with open("./docs/en/src/messages.md", "w") as f: + print(doc, file=f) + + +if __name__ == "__main__": + main() diff --git a/src/app.rs b/src/app.rs index dffd5b1..e8ffe12 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,1164 +1,38 @@ use crate::config::Config; use crate::config::Mode; +pub use crate::directory_buffer::DirectoryBuffer; use crate::explorer; use crate::input::{InputOperation, Key}; use crate::lua; -use crate::permissions::Permissions; +pub use crate::msg::in_::external::Command; +pub use crate::msg::in_::external::ExplorerConfig; +pub use crate::msg::in_::external::NodeFilter; +pub use crate::msg::in_::external::NodeFilterApplicable; +pub use crate::msg::in_::external::NodeSorter; +pub use crate::msg::in_::external::NodeSorterApplicable; +pub use crate::msg::in_::ExternalMsg; +pub use crate::msg::in_::InternalMsg; +pub use crate::msg::in_::MsgIn; +pub use crate::msg::out::MsgOut; +pub use crate::node::Node; +pub use crate::node::ResolvedNode; +pub use crate::pipe::Pipe; use crate::ui::Layout; use anyhow::{bail, Result}; use chrono::{DateTime, Local}; -use humansize::{file_size_opts as options, FileSize}; use indexmap::set::IndexSet; use serde::{Deserialize, Serialize}; -use std::cmp::Ordering; use std::collections::HashMap; use std::collections::VecDeque; use std::env; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use tui_input::{Input, InputRequest}; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const TEMPLATE_TABLE_ROW: &str = "TEMPLATE_TABLE_ROW"; pub const UNSUPPORTED_STR: &str = "???"; -fn to_humansize(size: u64) -> String { - size.file_size(options::CONVENTIONAL) - .unwrap_or_else(|_| format!("{} B", size)) -} - -fn mime_essence(path: &Path, is_dir: bool) -> String { - if is_dir { - String::from("inode/directory") - } else { - mime_guess::from_path(&path) - .first() - .map(|m| m.essence_str().to_string()) - .unwrap_or_default() - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Pipe { - pub path: String, - pub msg_in: String, - pub selection_out: String, - pub result_out: String, - pub directory_nodes_out: String, - pub global_help_menu_out: String, - pub logs_out: String, - pub history_out: String, -} - -impl Pipe { - fn from_session_path(path: &str) -> Result { - let path = PathBuf::from(path).join("pipe"); - - let msg_in = path.join("msg_in").to_string_lossy().to_string(); - - let selection_out = - path.join("selection_out").to_string_lossy().to_string(); - - let result_out = path.join("result_out").to_string_lossy().to_string(); - - let directory_nodes_out = path - .join("directory_nodes_out") - .to_string_lossy() - .to_string(); - - let global_help_menu_out = path - .join("global_help_menu_out") - .to_string_lossy() - .to_string(); - - let logs_out = path.join("logs_out").to_string_lossy().to_string(); - - let history_out = - path.join("history_out").to_string_lossy().to_string(); - - Ok(Self { - path: path.to_string_lossy().to_string(), - msg_in, - selection_out, - result_out, - directory_nodes_out, - global_help_menu_out, - logs_out, - history_out, - }) - } -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct ResolvedNode { - pub absolute_path: String, - pub extension: String, - pub is_dir: bool, - pub is_file: bool, - pub is_readonly: bool, - pub mime_essence: String, - pub size: u64, - pub human_size: String, -} - -impl ResolvedNode { - pub fn from(path: PathBuf) -> Self { - let extension = path - .extension() - .map(|e| e.to_string_lossy().to_string()) - .unwrap_or_default(); - - let (is_dir, is_file, is_readonly, size) = path - .metadata() - .map(|m| { - (m.is_dir(), m.is_file(), m.permissions().readonly(), m.len()) - }) - .unwrap_or((false, false, false, 0)); - - let mime_essence = mime_essence(&path, is_dir); - let human_size = to_humansize(size); - - Self { - absolute_path: path.to_string_lossy().to_string(), - extension, - is_dir, - is_file, - is_readonly, - mime_essence, - size, - human_size, - } - } -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct Node { - pub parent: String, - pub relative_path: String, - pub absolute_path: String, - pub extension: String, - pub is_dir: bool, - pub is_file: bool, - pub is_symlink: bool, - pub is_broken: bool, - pub is_readonly: bool, - pub mime_essence: String, - pub size: u64, - pub human_size: String, - pub permissions: Permissions, - pub canonical: Option, - pub symlink: Option, -} - -impl Node { - pub fn new(parent: String, relative_path: String) -> Self { - let absolute_path = PathBuf::from(&parent) - .join(&relative_path) - .to_string_lossy() - .to_string(); - - let path = PathBuf::from(&absolute_path); - - let extension = path - .extension() - .map(|e| e.to_string_lossy().to_string()) - .unwrap_or_default(); - - let (is_broken, maybe_canonical_meta) = path - .canonicalize() - .map(|p| (false, Some(ResolvedNode::from(p)))) - .unwrap_or_else(|_| (true, None)); - - let (is_symlink, is_dir, is_file, is_readonly, size, permissions) = - path.symlink_metadata() - .map(|m| { - ( - m.file_type().is_symlink(), - m.is_dir(), - m.is_file(), - m.permissions().readonly(), - m.len(), - Permissions::from(&m), - ) - }) - .unwrap_or_else(|_| { - (false, false, false, false, 0, Permissions::default()) - }); - - let mime_essence = mime_essence(&path, is_dir); - let human_size = to_humansize(size); - - Self { - parent, - relative_path, - absolute_path, - extension, - is_dir, - is_file, - is_symlink, - is_broken, - is_readonly, - mime_essence, - size, - human_size, - permissions, - canonical: maybe_canonical_meta.clone(), - symlink: if is_symlink { - maybe_canonical_meta - } else { - None - }, - } - } -} - -impl Ord for Node { - fn cmp(&self, other: &Self) -> Ordering { - // Notice that the we flip the ordering on costs. - // In case of a tie we compare positions - this step is necessary - // to make implementations of `PartialEq` and `Ord` consistent. - other.relative_path.cmp(&self.relative_path) - } -} - -impl PartialOrd for Node { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct DirectoryBuffer { - pub parent: String, - pub nodes: Vec, - pub total: usize, - pub focus: usize, -} - -impl DirectoryBuffer { - pub fn new(parent: String, nodes: Vec, focus: usize) -> Self { - let total = nodes.len(); - Self { - parent, - nodes, - total, - focus, - } - } - - pub fn focused_node(&self) -> Option<&Node> { - self.nodes.get(self.focus) - } -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum InternalMsg { - AddLastFocus(String, Option), - SetDirectory(DirectoryBuffer), - HandleKey(Key), -} - -#[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, - - ByCanonicalAbsolutePath, - ByICanonicalAbsolutePath, - ByCanonicalExtension, - ByCanonicalIsDir, - ByCanonicalIsFile, - ByCanonicalIsReadonly, - ByCanonicalMimeEssence, - ByCanonicalSize, - - BySymlinkAbsolutePath, - ByISymlinkAbsolutePath, - BySymlinkExtension, - BySymlinkIsDir, - BySymlinkIsFile, - BySymlinkIsReadonly, - BySymlinkMimeEssence, - BySymlinkSize, -} - -#[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 { - 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::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::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)), - }; - 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, - - AbsolutePathIs, - AbsolutePathIsNot, - - IAbsolutePathIs, - IAbsolutePathIsNot, - - AbsolutePathDoesStartWith, - AbsolutePathDoesNotStartWith, - - IAbsolutePathDoesStartWith, - IAbsolutePathDoesNotStartWith, - - AbsolutePathDoesContain, - AbsolutePathDoesNotContain, - - IAbsolutePathDoesContain, - IAbsolutePathDoesNotContain, - - AbsolutePathDoesEndWith, - AbsolutePathDoesNotEndWith, - - IAbsolutePathDoesEndWith, - IAbsolutePathDoesNotEndWith, -} - -impl NodeFilter { - fn apply(&self, node: &Node, input: &str) -> 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::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()), - } - } -} - -#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct NodeFilterApplicable { - pub filter: NodeFilter, - pub input: String, -} - -impl NodeFilterApplicable { - pub fn new(filter: NodeFilter, input: String) -> Self { - Self { filter, input } - } - - fn apply(&self, node: &Node) -> bool { - self.filter.apply(node, &self.input) - } -} - -#[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)] -pub enum ExternalMsg { - /// Explore the present working directory and register the filtered nodes. - /// This operation is expensive. So, try to avoid using it too often. - 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. - 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. - ExploreParentsAsync, - - /// Refresh the UI. - /// 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. - Refresh, - - /// Clears the screen. - ClearScreen, - - /// Focus next node. - FocusNext, - - /// Focus on the `n`th node relative to the current focus where `n` is a given value. - /// - /// **Example:** `FocusNextByRelativeIndex: 2` - FocusNextByRelativeIndex(usize), - - /// Focus on the `n`th node relative to the current focus where `n` is read from - /// the input buffer. - FocusNextByRelativeIndexFromInput, - - /// Focus on the previous item. - FocusPrevious, - - /// Focus on the `-n`th node relative to the current focus where `n` is a given value. - /// - /// **Example:** `FocusPreviousByRelativeIndex: 2` - FocusPreviousByRelativeIndex(usize), - - /// Focus on the `-n`th node relative to the current focus where `n` is read from - /// the input buffer. - FocusPreviousByRelativeIndexFromInput, - - /// Focus on the first node. - FocusFirst, - - /// Focus on the last node. - FocusLast, - - /// Focus on the given path. - /// - /// **Example:** `FocusPath: /tmp` - FocusPath(String), - - /// Focus on the path read from input buffer. - FocusPathFromInput, - - /// Focus on the absolute `n`th node where `n` is a given value. - /// - /// **Example:** `FocusByIndex: 2` - FocusByIndex(usize), - - /// Focus on the absolute `n`th node where `n` is read from the input buffer. - FocusByIndexFromInput, - - /// Focus on the file by name from the present working directory. - /// - /// **Example:** `FocusByFileName: README.md` - FocusByFileName(String), - - /// Change the present working directory ($PWD) - /// - /// **Example:** `ChangeDirectory: /tmp` - ChangeDirectory(String), - - /// Enter into the currently focused path if it's a directory. - Enter, - - /// Go back to the parent directory. - Back, - - /// Go to the last path visited. - LastVisitedPath, - - /// Go to the next path visited. - NextVisitedPath, - - /// Follow the symlink under focus to its actual location. - FollowSymlink, - - /// Update the input buffer using cursor based operations. - /// - /// **Example:** `UpdateInputBuffer: GoToPreviousWord` - UpdateInputBuffer(InputOperation), - - /// Update the input buffer from given key - UpdateInputBufferFromKey, - - /// Append/buffer the given string into the input buffer. - /// - /// **Example:** `BufferInput: foo` - BufferInput(String), - - /// Append/buffer the characted read from a keyboard input into the - /// input buffer. - 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. - /// - /// **Example:** `SetInputBuffer: foo` - SetInputBuffer(String), - - /// Remove input buffer's last character. - RemoveInputBufferLastCharacter, - - /// Remove input buffer's last word. - RemoveInputBufferLastWord, - - /// Reset the input buffer back to null. It will not show in the UI. - ResetInputBuffer, - - /// Switch input mode. - /// It clears the input buffer. - /// - /// > **NOTE:** To be specific about which mode to switch to, use `SwitchModeBuiltin` or - /// `SwitchModeCustom` instead. - /// - /// **Example:** `SwitchMode: default` - SwitchMode(String), - - /// Switch input mode. - /// It keeps the input buffer. - /// - /// > **NOTE:** To be specific about which mode to switch to, use - /// `SwitchModeBuiltinKeepingInputBuffer` or - /// `SwitchModeCustomKeepingInputBuffer` instead. - /// - /// **Example:** `SwitchModeKeepingInputBuffer: default` - SwitchModeKeepingInputBuffer(String), - - /// Switch to a builtin mode. - /// It clears the input buffer. - /// - /// **Example:** `SwitchModeBuiltin: default` - SwitchModeBuiltin(String), - - /// Switch to a builtin mode. - /// It keeps the input buffer. - /// - /// **Example:** `SwitchModeBuiltinKeepingInputBuffer: default` - SwitchModeBuiltinKeepingInputBuffer(String), - - /// Switch to a custom mode. - /// It clears the input buffer. - /// - /// **Example:** `SwitchModeCustom: my_custom_mode` - SwitchModeCustom(String), - - /// Switch to a custom mode. - /// It keeps the input buffer. - /// - /// **Example:** `SwitchModeCustomKeepingInputBuffer: my_custom_mode` - SwitchModeCustomKeepingInputBuffer(String), - - /// Pop the last mode from the history and switch to it. - /// It clears the input buffer. - PopMode, - - /// Pop the last mode from the history and switch to it. - /// It keeps the input buffer. - PopModeKeepingInputBuffer, - - /// Switch layout. - /// - /// > **NOTE:** To be specific about which layout to switch to, use `SwitchLayoutBuiltin` or - /// `SwitchLayoutCustom` instead. - /// - /// **Example:** `SwitchLayout: default` - SwitchLayout(String), - - /// Switch to a builtin layout. - /// - /// **Example:** `SwitchLayoutBuiltin: default` - SwitchLayoutBuiltin(String), - - /// Switch to a custom layout. - /// - /// **Example:** `SwitchLayoutCustom: my_custom_layout` - SwitchLayoutCustom(String), - - /// 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. - /// - /// **Example:** `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. - /// - /// **Example:** `CallSilently: {command: tput, args: ["bell"]}` - CallSilently(Command), - - /// An alias to `Call: {command: bash, args: ["-c", "${command}"], silent: false}` - /// where ${command} is the given value. - /// - /// **Example:** `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. - /// - /// **Example:** `BashExecSilently: "tput bell"` - BashExecSilently(String), - - /// Call a Lua function. - /// A `CallLuaArg` 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. - /// - /// **Example:** `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. - /// - /// **Example:** `CallLuaSilently: custom.some_custom_function` - CallLuaSilently(String), - - /// Execute Lua code without needing to define a function. - /// However, `CallLuaArg` won't be available. - /// - /// **Example:** `LuaEval: "return io.read()"` - LuaEval(String), - - /// Like `LuaEval` but without the flicker. The stdin, stdout - /// stderr will be piped to null. So it's non-interactive. - /// - /// **Example:** `LuaEvalSilently: "return { LogInfo = 'foo' }"` - LuaEvalSilently(String), - - /// Select the focused node. - Select, - - /// Select all the visible nodes. - SelectAll, - - /// Select the given path. - /// - /// **Example:** `SelectPath: "/tmp"` - SelectPath(String), - - /// Unselect the focused node. - UnSelect, - - /// Unselect all the visible nodes. - UnSelectAll, - - /// UnSelect the given path. - /// - /// **Example:** `UnSelectPath: "/tmp"` - UnSelectPath(String), - - /// Toggle selection on the focused node. - ToggleSelection, - - /// Toggle between select all and unselect all. - ToggleSelectAll, - - /// Toggle selection by file path. - /// - /// **Example:** `ToggleSelectionByPath: "/tmp"` - ToggleSelectionByPath(String), - - /// Clear the selection. - ClearSelection, - - /// Add a filter to exclude nodes while exploring directories. - /// - /// **Example:** `AddNodeFilter: {filter: RelativePathDoesStartWith, input: foo}` - AddNodeFilter(NodeFilterApplicable), - - /// Remove an existing filter. - /// - /// **Example:** `RemoveNodeFilter: {filter: RelativePathDoesStartWith, input: foo}` - RemoveNodeFilter(NodeFilterApplicable), - - /// Remove a filter if it exists, else, add a it. - /// - /// **Example:** `ToggleNodeFilter: {filter: RelativePathDoesStartWith, input: foo}` - ToggleNodeFilter(NodeFilterApplicable), - - /// Add a node filter reading the input from the buffer. - /// - /// **Example:** `AddNodeFilterFromInput: RelativePathDoesStartWith` - AddNodeFilterFromInput(NodeFilter), - - /// Remove a node filter reading the input from the buffer. - /// - /// **Example:** `RemoveNodeFilterFromInput: RelativePathDoesStartWith` - RemoveNodeFilterFromInput(NodeFilter), - - /// Remove the last node filter. - RemoveLastNodeFilter, - - /// Reset the node filters back to the default configuration. - ResetNodeFilters, - - /// Clear all the node filters. - ClearNodeFilters, - - /// Add a sorter to sort nodes while exploring directories. - /// - /// **Example:** `AddNodeSorter: {sorter: ByRelativePath, reverse: false}` - AddNodeSorter(NodeSorterApplicable), - - /// Remove an existing sorter. - /// - /// **Example:** `RemoveNodeSorter: ByRelativePath` - RemoveNodeSorter(NodeSorter), - - /// Reverse a node sorter. - /// - /// **Example:** `ReverseNodeSorter: ByRelativePath` - ReverseNodeSorter(NodeSorter), - - /// Remove a sorter if it exists, else, add a it. - /// - /// **Example:** `ToggleSorterSorter: {sorter: ByRelativePath, reverse: false}` - ToggleNodeSorter(NodeSorterApplicable), - - /// Reverse the node sorters. - ReverseNodeSorters, - - /// Remove the last node sorter. - RemoveLastNodeSorter, - - /// Reset the node sorters back to the default configuration. - ResetNodeSorters, - - /// Clear all the node sorters. - ClearNodeSorters, - - /// Enable mouse - EnableMouse, - - /// Disable mouse - DisableMouse, - - /// Toggle mouse - ToggleMouse, - - /// Start piping the focused path to the given fifo path - /// - /// **Example:** `StartFifo: /tmp/xplr.fifo` - StartFifo(String), - - /// Close the active fifo and stop piping. - StopFifo, - - /// Toggle betwen {Start|Stop}Fifo - ToggleFifo(String), - - /// Log information message. - /// - /// **Example:** `LogInfo: launching satellite` - LogInfo(String), - - /// Log a success message. - /// - /// **Example:** `LogSuccess: satellite reached destination`. - LogSuccess(String), - - /// Log an warning message. - /// - /// **Example:** `LogWarning: satellite is heating` - LogWarning(String), - - /// Log an error message. - /// - /// **Example:** `LogError: satellite crashed` - LogError(String), - - /// Quit with returncode zero (success). - Quit, - - /// Print $PWD and quit. - PrintPwdAndQuit, - - /// Print the path under focus and quit. It can be empty string if there's nothing to focus. - PrintFocusPathAndQuit, - - /// Print the selected paths and quit. It can be empty is no path is selected. - PrintSelectionAndQuit, - - /// Print the selected paths if it's not empty, else, print the focused node's path. - PrintResultAndQuit, - - /// Print the state of application in YAML format. Helpful for debugging or generating - /// the default configuration file. - PrintAppStateAndQuit, - - /// Write the application state to a file, without quitting. Also helpful for debugging. - Debug(String), - - /// Terminate the application with a non-zero return code. - 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, Eq, PartialEq, Serialize, Deserialize)] -pub enum MsgIn { - Internal(InternalMsg), - External(ExternalMsg), -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Command { - pub command: String, - - #[serde(default)] - pub args: Vec, -} - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub enum MsgOut { - ExplorePwdAsync, - ExploreParentsAsync, - Refresh, - ClearScreen, - Quit, - Debug(String), - Call(Command), - CallSilently(Command), - CallLua(String), - CallLuaSilently(String), - LuaEval(String), - LuaEvalSilently(String), - Enque(Task), - EnableMouse, - DisableMouse, - ToggleMouse, - StartFifo(String), - StopFifo, - ToggleFifo(String), - PrintPwdAndQuit, - PrintFocusPathAndQuit, - PrintSelectionAndQuit, - PrintResultAndQuit, - PrintAppStateAndQuit, -} - #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Task { pub msg: MsgIn, diff --git a/src/directory_buffer.rs b/src/directory_buffer.rs new file mode 100644 index 0000000..4d617c5 --- /dev/null +++ b/src/directory_buffer.rs @@ -0,0 +1,26 @@ +use crate::node::Node; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct DirectoryBuffer { + pub parent: String, + pub nodes: Vec, + pub total: usize, + pub focus: usize, +} + +impl DirectoryBuffer { + pub fn new(parent: String, nodes: Vec, focus: usize) -> Self { + let total = nodes.len(); + Self { + parent, + nodes, + total, + focus, + } + } + + pub fn focused_node(&self) -> Option<&Node> { + self.nodes.get(self.focus) + } +} diff --git a/src/lib.rs b/src/lib.rs index dc90c75..606b5fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,11 +5,15 @@ pub mod app; pub mod cli; pub mod config; +pub mod directory_buffer; pub mod event_reader; pub mod explorer; pub mod input; pub mod lua; +pub mod msg; +pub mod node; pub mod permissions; +pub mod pipe; pub mod pipe_reader; pub mod pwd_watcher; pub mod runner; diff --git a/src/msg/in_/external.rs b/src/msg/in_/external.rs new file mode 100644 index 0000000..08cb4aa --- /dev/null +++ b/src/msg/in_/external.rs @@ -0,0 +1,1409 @@ +use crate::{app::Node, input::InputOperation}; +use indexmap::IndexSet; +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 ------------------------------------------------------ + + /// 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, + + ByCanonicalAbsolutePath, + ByICanonicalAbsolutePath, + ByCanonicalExtension, + ByCanonicalIsDir, + ByCanonicalIsFile, + ByCanonicalIsReadonly, + ByCanonicalMimeEssence, + ByCanonicalSize, + + BySymlinkAbsolutePath, + ByISymlinkAbsolutePath, + BySymlinkExtension, + BySymlinkIsDir, + BySymlinkIsFile, + BySymlinkIsReadonly, + BySymlinkMimeEssence, + BySymlinkSize, +} + +#[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::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::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)), + }; + 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, + + AbsolutePathIs, + AbsolutePathIsNot, + + IAbsolutePathIs, + IAbsolutePathIsNot, + + AbsolutePathDoesStartWith, + AbsolutePathDoesNotStartWith, + + IAbsolutePathDoesStartWith, + IAbsolutePathDoesNotStartWith, + + AbsolutePathDoesContain, + AbsolutePathDoesNotContain, + + IAbsolutePathDoesContain, + IAbsolutePathDoesNotContain, + + AbsolutePathDoesEndWith, + AbsolutePathDoesNotEndWith, + + IAbsolutePathDoesEndWith, + IAbsolutePathDoesNotEndWith, +} + +impl NodeFilter { + fn apply(&self, node: &Node, input: &str) -> 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::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()), + } + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct NodeFilterApplicable { + pub filter: NodeFilter, + pub input: String, +} + +impl NodeFilterApplicable { + pub fn new(filter: NodeFilter, input: String) -> Self { + Self { filter, input } + } + + fn apply(&self, node: &Node) -> bool { + self.filter.apply(node, &self.input) + } +} + +#[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, +} diff --git a/src/msg/in_/internal.rs b/src/msg/in_/internal.rs new file mode 100644 index 0000000..660a4db --- /dev/null +++ b/src/msg/in_/internal.rs @@ -0,0 +1,10 @@ +use crate::app::DirectoryBuffer; +use crate::input::Key; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum InternalMsg { + AddLastFocus(String, Option), + SetDirectory(DirectoryBuffer), + HandleKey(Key), +} diff --git a/src/msg/in_/mod.rs b/src/msg/in_/mod.rs new file mode 100644 index 0000000..6837d91 --- /dev/null +++ b/src/msg/in_/mod.rs @@ -0,0 +1,12 @@ +pub mod external; +pub mod internal; + +pub use external::ExternalMsg; +pub use internal::InternalMsg; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum MsgIn { + Internal(internal::InternalMsg), + External(external::ExternalMsg), +} diff --git a/src/msg/mod.rs b/src/msg/mod.rs new file mode 100644 index 0000000..c576752 --- /dev/null +++ b/src/msg/mod.rs @@ -0,0 +1,2 @@ +pub mod in_; +pub mod out; diff --git a/src/msg/out/mod.rs b/src/msg/out/mod.rs new file mode 100644 index 0000000..ba47fa0 --- /dev/null +++ b/src/msg/out/mod.rs @@ -0,0 +1,31 @@ +use serde::{Deserialize, Serialize}; + +use crate::app::{Command, Task}; + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum MsgOut { + ExplorePwdAsync, + ExploreParentsAsync, + Refresh, + ClearScreen, + Quit, + Debug(String), + Call(Command), + CallSilently(Command), + CallLua(String), + CallLuaSilently(String), + LuaEval(String), + LuaEvalSilently(String), + Enque(Task), + EnableMouse, + DisableMouse, + ToggleMouse, + StartFifo(String), + StopFifo, + ToggleFifo(String), + PrintPwdAndQuit, + PrintFocusPathAndQuit, + PrintSelectionAndQuit, + PrintResultAndQuit, + PrintAppStateAndQuit, +} diff --git a/src/node.rs b/src/node.rs new file mode 100644 index 0000000..8394cad --- /dev/null +++ b/src/node.rs @@ -0,0 +1,159 @@ +use crate::permissions::Permissions; +use humansize::{file_size_opts as options, FileSize}; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +use std::path::{Path, PathBuf}; + +fn to_humansize(size: u64) -> String { + size.file_size(options::CONVENTIONAL) + .unwrap_or_else(|_| format!("{} B", size)) +} + +fn mime_essence(path: &Path, is_dir: bool) -> String { + if is_dir { + String::from("inode/directory") + } else { + mime_guess::from_path(&path) + .first() + .map(|m| m.essence_str().to_string()) + .unwrap_or_default() + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ResolvedNode { + pub absolute_path: String, + pub extension: String, + pub is_dir: bool, + pub is_file: bool, + pub is_readonly: bool, + pub mime_essence: String, + pub size: u64, + pub human_size: String, +} + +impl ResolvedNode { + pub fn from(path: PathBuf) -> Self { + let extension = path + .extension() + .map(|e| e.to_string_lossy().to_string()) + .unwrap_or_default(); + + let (is_dir, is_file, is_readonly, size) = path + .metadata() + .map(|m| { + (m.is_dir(), m.is_file(), m.permissions().readonly(), m.len()) + }) + .unwrap_or((false, false, false, 0)); + + let mime_essence = mime_essence(&path, is_dir); + let human_size = to_humansize(size); + + Self { + absolute_path: path.to_string_lossy().to_string(), + extension, + is_dir, + is_file, + is_readonly, + mime_essence, + size, + human_size, + } + } +} + +#[derive(Debug, Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct Node { + pub parent: String, + pub relative_path: String, + pub absolute_path: String, + pub extension: String, + pub is_dir: bool, + pub is_file: bool, + pub is_symlink: bool, + pub is_broken: bool, + pub is_readonly: bool, + pub mime_essence: String, + pub size: u64, + pub human_size: String, + pub permissions: Permissions, + pub canonical: Option, + pub symlink: Option, +} + +impl Node { + pub fn new(parent: String, relative_path: String) -> Self { + let absolute_path = PathBuf::from(&parent) + .join(&relative_path) + .to_string_lossy() + .to_string(); + + let path = PathBuf::from(&absolute_path); + + let extension = path + .extension() + .map(|e| e.to_string_lossy().to_string()) + .unwrap_or_default(); + + let (is_broken, maybe_canonical_meta) = path + .canonicalize() + .map(|p| (false, Some(ResolvedNode::from(p)))) + .unwrap_or_else(|_| (true, None)); + + let (is_symlink, is_dir, is_file, is_readonly, size, permissions) = + path.symlink_metadata() + .map(|m| { + ( + m.file_type().is_symlink(), + m.is_dir(), + m.is_file(), + m.permissions().readonly(), + m.len(), + Permissions::from(&m), + ) + }) + .unwrap_or_else(|_| { + (false, false, false, false, 0, Permissions::default()) + }); + + let mime_essence = mime_essence(&path, is_dir); + let human_size = to_humansize(size); + + Self { + parent, + relative_path, + absolute_path, + extension, + is_dir, + is_file, + is_symlink, + is_broken, + is_readonly, + mime_essence, + size, + human_size, + permissions, + canonical: maybe_canonical_meta.clone(), + symlink: if is_symlink { + maybe_canonical_meta + } else { + None + }, + } + } +} + +impl Ord for Node { + fn cmp(&self, other: &Self) -> Ordering { + // Notice that the we flip the ordering on costs. + // In case of a tie we compare positions - this step is necessary + // to make implementations of `PartialEq` and `Ord` consistent. + other.relative_path.cmp(&self.relative_path) + } +} + +impl PartialOrd for Node { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/src/pipe.rs b/src/pipe.rs new file mode 100644 index 0000000..7eec981 --- /dev/null +++ b/src/pipe.rs @@ -0,0 +1,55 @@ +use std::path::PathBuf; + +use anyhow::Result; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Pipe { + pub path: String, + pub msg_in: String, + pub selection_out: String, + pub result_out: String, + pub directory_nodes_out: String, + pub global_help_menu_out: String, + pub logs_out: String, + pub history_out: String, +} + +impl Pipe { + pub fn from_session_path(path: &str) -> Result { + let path = PathBuf::from(path).join("pipe"); + + let msg_in = path.join("msg_in").to_string_lossy().to_string(); + + let selection_out = + path.join("selection_out").to_string_lossy().to_string(); + + let result_out = path.join("result_out").to_string_lossy().to_string(); + + let directory_nodes_out = path + .join("directory_nodes_out") + .to_string_lossy() + .to_string(); + + let global_help_menu_out = path + .join("global_help_menu_out") + .to_string_lossy() + .to_string(); + + let logs_out = path.join("logs_out").to_string_lossy().to_string(); + + let history_out = + path.join("history_out").to_string_lossy().to_string(); + + Ok(Self { + path: path.to_string_lossy().to_string(), + msg_in, + selection_out, + result_out, + directory_nodes_out, + global_help_menu_out, + logs_out, + history_out, + }) + } +}