From 5370cc2e8c765846f1f4c262a92f874f3f158902 Mon Sep 17 00:00:00 2001 From: Arijit Basu Date: Fri, 3 Jun 2022 21:34:49 +0530 Subject: [PATCH] Go to path and native auto completion on tab - BREAKING: Rename mode `create directory` and `create file` to `create_directory` and `create_file`. - Add key binding `gp` to go to a given path from input. - Add function `fn.builtin.try_complete_path` to auto complete the path in input buffer. - Use `tab` to auto complete path in `rename`, `create_file`, `create_directory` and `go_to_path` modes. - Show different prompts in different modes. And some cleanup. --- docs/en/src/awesome-hacks.md | 2 +- docs/en/src/awesome-plugins.md | 2 +- docs/en/src/configuration.md | 4 + docs/en/src/modes.md | 6 ++ src/app.rs | 26 +----- src/config.rs | 143 +--------------------------- src/init.lua | 166 ++++++++++++++++++++++++++++++--- 7 files changed, 168 insertions(+), 181 deletions(-) diff --git a/docs/en/src/awesome-hacks.md b/docs/en/src/awesome-hacks.md index 1786d9c..d3f6a99 100644 --- a/docs/en/src/awesome-hacks.md +++ b/docs/en/src/awesome-hacks.md @@ -460,8 +460,8 @@ xplr.fn.custom.preview_pane.render = function(ctx) end end ``` - + ## Also See: diff --git a/docs/en/src/awesome-plugins.md b/docs/en/src/awesome-plugins.md index 56de554..08f472a 100644 --- a/docs/en/src/awesome-plugins.md +++ b/docs/en/src/awesome-plugins.md @@ -15,7 +15,7 @@ of the following plugins work for you, it's very easy to - [**prncss-xyz/type-to-nav.xplr**][28] Inspired by [nnn's type-to-nav mode][29] for xplr, with some tweaks. - [**igorepst/term.xplr**][39] Terminal integration for xplr -- [**sayanarijit/wl-clipboard.xplr**][52] Copy and paste with system clipboard using wl-clipboard +- [**sayanarijit/wl-clipboard.xplr**][52] Copy and paste with system clipboard using wl-clipboard - [**dtomvan/xpm.xplr**][47] The XPLR Plugin Manager ### Integration diff --git a/docs/en/src/configuration.md b/docs/en/src/configuration.md index b0bbcdd..307c011 100644 --- a/docs/en/src/configuration.md +++ b/docs/en/src/configuration.md @@ -43,6 +43,10 @@ See: [Lua Function Calls](https://xplr.dev/en/lua-function-calls) As always, `xplr.fn.builtin` is where the built-in functions are defined that can be overwritten. +#### xplr.fn.builtin.try_complete_path + +Tries to auto complete the path in the input buffer + #### xplr.fn.builtin.fmt_general_table_row_cols_0 Renders the first column in the table diff --git a/docs/en/src/modes.md b/docs/en/src/modes.md index 2ef6204..40793c7 100644 --- a/docs/en/src/modes.md +++ b/docs/en/src/modes.md @@ -29,6 +29,12 @@ The builtin recover mode. Type: [Mode](https://xplr.dev/en/mode) +#### xplr.config.modes.builtin.go_to_path + +The builtin go to path mode. + +Type: [Mode](https://xplr.dev/en/mode) + #### xplr.config.modes.builtin.selection_ops The builtin selection ops mode. diff --git a/src/app.rs b/src/app.rs index 658cd85..3ab177c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -956,7 +956,7 @@ impl App { } fn push_mode(mut self) -> Self { - if self.mode != self.config.modes.builtin.recover + if Some(&self.mode) != self.config.modes.builtin.get("recover") && self .last_modes .last() @@ -1492,29 +1492,7 @@ impl App { let builtin = &self.config.modes.builtin; let custom = &self.config.modes.custom; - [ - &builtin.default, - &builtin.debug_error, - &builtin.recover, - &builtin.selection_ops, - &builtin.create, - &builtin.create_directory, - &builtin.create_file, - &builtin.number, - &builtin.go_to, - &builtin.rename, - &builtin.duplicate_as, - &builtin.delete, - &builtin.action, - &builtin.search, - &builtin.filter, - &builtin.relative_path_does_match_regex, - &builtin.relative_path_does_not_match_regex, - &builtin.sort, - &builtin.switch_layout, - &builtin.quit, - ] - .iter().map(|m| (&m.name, m.to_owned())) + builtin.iter() .chain(custom.iter()) .map(|(name, mode)| { let help = mode diff --git a/src/config.rs b/src/config.rs index d6f67f1..80cfdda 100644 --- a/src/config.rs +++ b/src/config.rs @@ -498,119 +498,11 @@ impl Mode { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct BuiltinModesConfig { - #[serde(default)] - pub default: Mode, - - #[serde(default)] - pub debug_error: Mode, - - #[serde(default)] - pub recover: Mode, - - #[serde(default)] - pub selection_ops: Mode, - - #[serde(default)] - pub create: Mode, - - #[serde(default)] - pub create_directory: Mode, - - #[serde(default)] - pub create_file: Mode, - - #[serde(default)] - pub number: Mode, - - #[serde(default)] - pub go_to: Mode, - - #[serde(default)] - pub rename: Mode, - - #[serde(default)] - pub duplicate_as: Mode, - - #[serde(default)] - pub delete: Mode, - - #[serde(default)] - pub action: Mode, - - #[serde(default)] - pub search: Mode, - - #[serde(default)] - pub filter: Mode, - - #[serde(default)] - pub relative_path_does_match_regex: Mode, - - #[serde(default)] - pub relative_path_does_not_match_regex: Mode, - - #[serde(default)] - pub sort: Mode, - - #[serde(default)] - pub switch_layout: Mode, - - #[serde(default)] - pub quit: Mode, -} - -impl BuiltinModesConfig { - pub fn get(&self, name: &str) -> Option<&Mode> { - match name { - "default" => Some(&self.default), - "debug_error" => Some(&self.debug_error), - "recover" => Some(&self.recover), - "selection ops" => Some(&self.selection_ops), - "selection_ops" => Some(&self.selection_ops), - "create" => Some(&self.create), - "create file" => Some(&self.create_file), - "create_file" => Some(&self.create_file), - "create directory" => Some(&self.create_directory), - "create_directory" => Some(&self.create_directory), - "number" => Some(&self.number), - "go to" => Some(&self.go_to), - "go_to" => Some(&self.go_to), - "rename" => Some(&self.rename), - "duplicate as" => Some(&self.duplicate_as), - "duplicate_as" => Some(&self.duplicate_as), - "delete" => Some(&self.delete), - "action" => Some(&self.action), - "search" => Some(&self.search), - "sort" => Some(&self.sort), - "filter" => Some(&self.filter), - "relative_path_does_match_regex" => { - Some(&self.relative_path_does_match_regex) - } - "relative path does match regex" => { - Some(&self.relative_path_does_match_regex) - } - "relative_path_does_not_match_regex" => { - Some(&self.relative_path_does_not_match_regex) - } - "relative path does not match regex" => { - Some(&self.relative_path_does_not_match_regex) - } - "switch layout" => Some(&self.switch_layout), - "switch_layout" => Some(&self.switch_layout), - "quit" => Some(&self.quit), - _ => None, - } - } -} - #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct ModesConfig { #[serde(default)] - pub builtin: BuiltinModesConfig, + pub builtin: HashMap, #[serde(default)] pub custom: HashMap, @@ -652,42 +544,11 @@ impl PanelUiConfig { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct BuiltinLayoutsConfig { - #[serde(default)] - pub default: Layout, - - #[serde(default)] - pub no_help: Layout, - - #[serde(default)] - pub no_selection: Layout, - - #[serde(default)] - pub no_help_no_selection: Layout, -} - -impl BuiltinLayoutsConfig { - pub fn get(&self, name: &str) -> Option<&Layout> { - match name { - "default" => Some(&self.default), - "no_help" => Some(&self.no_help), - "no help" => Some(&self.no_help), - "no_selection" => Some(&self.no_selection), - "no selection" => Some(&self.no_selection), - "no_help_no_selection" => Some(&self.no_help_no_selection), - "no help no selection" => Some(&self.no_help_no_selection), - _ => None, - } - } -} - #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct LayoutsConfig { #[serde(default)] - pub builtin: BuiltinLayoutsConfig, + pub builtin: HashMap, #[serde(default)] pub custom: HashMap, diff --git a/src/init.lua b/src/init.lua index 626870a..d6682ac 100644 --- a/src/init.lua +++ b/src/init.lua @@ -1143,7 +1143,9 @@ xplr.config.modes.builtin.default = { { SwitchModeBuiltin = "rename" }, { BashExecSilently = [===[ - echo SetInputBuffer: "'"$(basename "${XPLR_FOCUS_PATH}")"'" >> "${XPLR_PIPE_MSG_IN:?}" + NAME=$(basename "${XPLR_FOCUS_PATH:?}") + echo SetInputPrompt: "'"${NAME:?} ❯ "'" >> "${XPLR_PIPE_MSG_IN:?}" + echo SetInputBuffer: "'"${NAME:?}"'" >> "${XPLR_PIPE_MSG_IN:?}" ]===], }, }, @@ -1202,6 +1204,7 @@ xplr.config.modes.builtin.default = { messages = { "PopMode", { SwitchModeBuiltin = "number" }, + { SetInputPrompt = ":" }, "BufferInputFromKey", }, }, @@ -1354,6 +1357,55 @@ xplr.config.modes.builtin.recover = { }, } +-- The builtin go to path mode. +-- +-- Type: [Mode](https://xplr.dev/en/mode) +xplr.config.modes.builtin.go_to_path = { + name = "go to path", + key_bindings = { + on_key = { + enter = { + help = "submit", + messages = { + { + BashExecSilently = [===[ + if [ -d "${XPLR_INPUT_BUFFER:?}" ]; then + echo ChangeDirectory: "'"$XPLR_INPUT_BUFFER"'" >> "${XPLR_PIPE_MSG_IN:?}" + else + echo FocusPath: "'"$XPLR_INPUT_BUFFER"'" >> "${XPLR_PIPE_MSG_IN:?}" + fi + ]===], + }, + "PopMode", + }, + }, + tab = { + help = "try complete", + messages = { + { CallLuaSilently = "builtin.try_complete_path" }, + }, + }, + ["ctrl-c"] = { + help = "terminate", + messages = { + "Terminate", + }, + }, + esc = { + help = "cancel", + messages = { + "PopMode", + }, + }, + }, + default = { + messages = { + "UpdateInputBufferFromKey", + }, + }, + }, +} + -- The builtin selection ops mode. -- -- Type: [Mode](https://xplr.dev/en/mode) @@ -1453,11 +1505,12 @@ xplr.config.modes.builtin.create = { "Terminate", }, }, - ["d"] = { + d = { help = "create directory", messages = { "PopMode", - { SwitchModeBuiltin = "create directory" }, + { SwitchModeBuiltin = "create_directory" }, + { SetInputPrompt = "ð ❯ " }, { SetInputBuffer = "" }, }, }, @@ -1467,11 +1520,12 @@ xplr.config.modes.builtin.create = { "PopMode", }, }, - ["f"] = { + f = { help = "create file", messages = { "PopMode", - { SwitchModeBuiltin = "create file" }, + { SwitchModeBuiltin = "create_file" }, + { SetInputPrompt = "ƒ ❯ " }, { SetInputBuffer = "" }, }, }, @@ -1492,8 +1546,14 @@ xplr.config.modes.builtin.create_directory = { "Terminate", }, }, + tab = { + help = "try complete", + messages = { + { CallLuaSilently = "builtin.try_complete_path" }, + }, + }, enter = { - help = "create directory", + help = "submit", messages = { { BashExecSilently = [===[ @@ -1537,8 +1597,14 @@ xplr.config.modes.builtin.create_file = { help = "terminate", messages = { "Terminate" }, }, + tab = { + help = "try complete", + messages = { + { CallLuaSilently = "builtin.try_complete_path" }, + }, + }, enter = { - help = "create file", + help = "submit", messages = { { BashExecSilently = [===[ @@ -1651,21 +1717,30 @@ xplr.config.modes.builtin.go_to = { "PopMode", }, }, - ["f"] = { + f = { help = "follow symlink", messages = { "FollowSymlink", "PopMode", }, }, - ["g"] = { + g = { help = "top", messages = { "FocusFirst", "PopMode", }, }, - ["x"] = { + p = { + help = "path", + messages = { + "PopMode", + { SwitchModeBuiltin = "go_to_path" }, + { SetInputBuffer = "" }, + { SetInputPrompt = "❯ " }, + }, + }, + x = { help = "open in gui", messages = { { @@ -1704,6 +1779,12 @@ xplr.config.modes.builtin.rename = { "Terminate", }, }, + tab = { + help = "try complete", + messages = { + { CallLuaSilently = "builtin.try_complete_path" }, + }, + }, enter = { help = "rename", messages = { @@ -1940,6 +2021,7 @@ xplr.config.modes.builtin.action = { messages = { "PopMode", { SwitchModeBuiltin = "number" }, + { SetInputPrompt = ":" }, "BufferInputFromKey", }, }, @@ -2108,7 +2190,7 @@ xplr.config.modes.builtin.filter = { }, }, enter = { - help = "done", + help = "submit", messages = { "PopMode", }, @@ -2161,7 +2243,7 @@ xplr.config.modes.builtin.relative_path_does_match_regex = { }, }, enter = { - help = "apply filter", + help = "submit", messages = { "PopMode", }, @@ -2200,7 +2282,7 @@ xplr.config.modes.builtin.relative_path_does_not_match_regex = { }, }, enter = { - help = "apply filter", + help = "submit", messages = { "PopMode", }, @@ -2351,7 +2433,7 @@ xplr.config.modes.builtin.sort = { }, }, enter = { - help = "done", + help = "submit", messages = { "PopMode", }, @@ -2526,6 +2608,62 @@ xplr.config.modes.custom = {} -- As always, `xplr.fn.builtin` is where the built-in functions are defined -- that can be overwritten. +-- Tries to auto complete the path in the input buffer +xplr.fn.builtin.try_complete_path = function(m) + if not m.input_buffer then + return + end + + local function splitlines(str) + local res = {} + for s in str:gmatch("[^\r\n]+") do + table.insert(res, s) + end + return res + end + + local function matches_all(str, files) + for _, p in ipairs(files) do + if string.sub(p, 1, #str) ~= str then + return false + end + end + return true + end + + local path = m.input_buffer + + local p = assert(io.popen(string.format("ls -d %q* 2>/dev/null", path))) + local out = p:read("*all") + p:close() + + local found = splitlines(out) + local count = #found + + if count == 0 then + return + elseif count == 1 then + return { + { SetInputBuffer = found[1] }, + } + else + local first = found[1] + while #first > #path and matches_all(path, found) do + path = string.sub(found[1], 1, #path + 1) + end + + if matches_all(path, found) then + return { + { SetInputBuffer = path }, + } + end + + return { + { SetInputBuffer = string.sub(path, 1, #path - 1) }, + } + end +end + -- Renders the first column in the table xplr.fn.builtin.fmt_general_table_row_cols_0 = function(m) local r = ""