From 5030358735848c4ffcc9414b597be310f67ade99 Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:03:34 +0200 Subject: [PATCH 1/6] refactored init module --- plugin/init.lua | 166 ++++++++++++++++++++++++++++-------------------- 1 file changed, 96 insertions(+), 70 deletions(-) diff --git a/plugin/init.lua b/plugin/init.lua index 381b0c9..c0fe10f 100644 --- a/plugin/init.lua +++ b/plugin/init.lua @@ -1,10 +1,18 @@ local wezterm = require("wezterm") local act = wezterm.action +local mux = wezterm.mux ----@alias action_callback any +---@class module +---@field zoxide_path string +local pub = { + zoxide_path = "zoxide", +} ----@type string -local zoxide_path = "zoxide" +local is_windows = string.find(wezterm.target_triple, "windows") ~= nil + +-- TODO: fix these +---@alias action_callback any +---@alias MuxWindow any ---@param label string ---@return string @@ -17,13 +25,11 @@ end ---@param cmd string ---@return string local run_child_process = function(cmd) - local is_windows = string.find(wezterm.target_triple, "windows") ~= nil - local success, stdout, stderr + local process_args = { os.getenv("SHELL"), "-c", cmd } if is_windows then - success, stdout, stderr = wezterm.run_child_process({ "cmd", "/c", cmd }) - else - success, stdout, stderr = wezterm.run_child_process({ os.getenv("SHELL"), "-c", cmd }) + process_args = { "cmd", "/c", cmd } end + local success, stdout, stderr = wezterm.run_child_process(process_args) if not success then wezterm.log_error("Child process '" .. cmd .. "' failed with stderr: '" .. stderr .. "'") @@ -31,95 +37,125 @@ local run_child_process = function(cmd) return stdout end ----@param extra_args? string ----@return { id: string, label: string }[] -local function get_zoxide_workspaces(extra_args) - if extra_args == nil then - extra_args = "" - end - local stdout = run_child_process(zoxide_path .. " query -l " .. extra_args) +---@alias InputSelector_choices { id: string, label: string }[] +---@alias workspace_ids table - local workspace_table = {} +---@param choice_table InputSelector_choices +---@return InputSelector_choices +---@return workspace_ids +local function get_workspace_elements(choice_table) local workspace_ids = {} - for _, workspace in ipairs(wezterm.mux.get_workspace_names()) do - table.insert(workspace_table, { + for _, workspace in ipairs(mux.get_workspace_names()) do + table.insert(choice_table, { id = workspace, label = workspace_formatter(workspace), }) workspace_ids[workspace] = true end + return choice_table, workspace_ids +end + +---@param choice_table InputSelector_choices +---@param opts? {extra_args: string?, workspace_ids: workspace_ids} +---@return InputSelector_choices +local function get_zoxide_elements(choice_table, opts) + if opts == nil then + opts = { extra_args = "", workspace_ids = {} } + end + + local stdout = run_child_process(pub.zoxide_path .. " query -l " .. (opts.extra_args or "")) + for _, path in ipairs(wezterm.split_by_newlines(stdout)) do local updated_path = string.gsub(path, wezterm.home_dir, "~") - if not workspace_ids[updated_path] then - table.insert(workspace_table, { + if not opts.workspace_ids[updated_path] then + table.insert(choice_table, { id = path, label = updated_path, }) end end - return workspace_table + return choice_table +end + +---@param workspace string +---@return MuxWindow +local function get_current_mux_window(workspace) + for _, mux_win in ipairs(mux.all_windows()) do + if mux_win:get_workspace() == workspace then + return mux_win + end + end + error("Could not find a workspace with the name: " .. workspace) +end + +---@generic T +---@param array T[] +---@param element T +---@return boolean +local function array_contains(array, element) + for _, value in ipairs(array) do + if element == value then + return true + end + end + return false end ---@param extra_args? string ---@return action_callback -local function workspace_switcher(extra_args) +function pub.switch_workspace(extra_args) return wezterm.action_callback(function(window, pane) wezterm.emit("smart_workspace_switcher.workspace_switcher.start", window) - local workspaces = get_zoxide_workspaces(extra_args) + local opts = { extra_args = extra_args } -- TODO: could be input instead + local choices = {} + choices, opts.workspace_ids = get_workspace_elements(choices) + choices = get_zoxide_elements(choices, opts) window:perform_action( act.InputSelector({ action = wezterm.action_callback(function(inner_window, inner_pane, id, label) if id and label then wezterm.emit("smart_workspace_switcher.workspace_switcher.selected", window, id, label) - local fullPath = string.gsub(label, "^~", wezterm.home_dir) - if fullPath:sub(1, 1) == "/" or fullPath:sub(3, 3) == "\\" then - -- if path is choosen + if array_contains(mux.get_workspace_names(), label) then + -- if workspace is choosen inner_window:perform_action( act.SwitchToWorkspace({ - name = label, - spawn = { - label = "Workspace: " .. label, - cwd = fullPath, - }, + name = id, }), inner_pane ) - for _, mux_win in ipairs(wezterm.mux.all_windows()) do - if mux_win:get_workspace() == label then - wezterm.emit( - "smart_workspace_switcher.workspace_switcher.created", - mux_win, - id, - label - ) - end - end - -- increment path score - run_child_process(zoxide_path .. " add " .. fullPath) + wezterm.emit( + "smart_workspace_switcher.workspace_switcher.chosen", + get_current_mux_window(id), + id, + label + ) else - -- if workspace is choosen + -- local original_path = string.gsub(label, "^~", wezterm.home_dir) -- TODO: swithced original_path to id + -- if path is choosen inner_window:perform_action( act.SwitchToWorkspace({ - name = id, + name = label, + spawn = { + label = "Workspace: " .. label, + cwd = id, + }, }), inner_pane ) - for _, mux_win in ipairs(wezterm.mux.all_windows()) do - if mux_win:get_workspace() == id then - wezterm.emit( - "smart_workspace_switcher.workspace_switcher.chosen", - mux_win, - id, - label - ) - end - end + wezterm.emit( + "smart_workspace_switcher.workspace_switcher.created", + get_current_mux_window(label), + id, + label + ) + -- increment path score + run_child_process(pub.zoxide_path .. " add " .. id) end end end), title = "Choose Workspace", - choices = workspaces, + choices = choices, fuzzy = true, }), pane @@ -129,27 +165,17 @@ end ---sets a default keybind to ALT-s ---@param config table -local function apply_to_config(config) +function pub.apply_to_config(config) table.insert(config.keys, { key = "s", mods = "ALT", - action = workspace_switcher(), + action = pub.switch_workspace(), }) end ----@param path string -local function set_zoxide_path(path) - zoxide_path = path -end - ---@param formatter fun(label: string): string -local function set_workspace_formatter(formatter) +function pub.set_workspace_formatter(formatter) workspace_formatter = formatter end -return { - apply_to_config = apply_to_config, - set_zoxide_path = set_zoxide_path, - set_workspace_formatter = set_workspace_formatter, - switch_workspace = workspace_switcher, -} +return pub From 712eea3148a102faa3cdbb863d61f6a59ae83b94 Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:45:37 +0200 Subject: [PATCH 2/6] add get_choices and major refactoring --- plugin/init.lua | 173 +++++++++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 75 deletions(-) diff --git a/plugin/init.lua b/plugin/init.lua index c0fe10f..33a1807 100644 --- a/plugin/init.lua +++ b/plugin/init.lua @@ -2,25 +2,29 @@ local wezterm = require("wezterm") local act = wezterm.action local mux = wezterm.mux ----@class module ----@field zoxide_path string -local pub = { - zoxide_path = "zoxide", -} - local is_windows = string.find(wezterm.target_triple, "windows") ~= nil --- TODO: fix these ---@alias action_callback any ---@alias MuxWindow any +---@alias Pane any ----@param label string ----@return string -local workspace_formatter = function(label) - return wezterm.format({ - { Text = "󱂬 : " .. label }, - }) -end +---@alias workspace_ids table +---@alias choice_opts {extra_args?: string, workspace_ids?: workspace_ids} +---@alias InputSelector_choices { id: string, label: string }[] + +---@class module +---@field zoxide_path string +---@field choices {get_zoxide_elements: (fun(choices: InputSelector_choices, opts: choice_opts?): InputSelector_choices), get_workspace_elements: (fun(choices: InputSelector_choices): (InputSelector_choices, workspace_ids))} +---@field workspace_formatter fun(label: string): string +local pub = { + zoxide_path = "zoxide", + choices = {}, + workspace_formatter = function(label) + return wezterm.format({ + { Text = "󱂬 : " .. label }, + }) + end, +} ---@param cmd string ---@return string @@ -37,18 +41,14 @@ local run_child_process = function(cmd) return stdout end ----@alias InputSelector_choices { id: string, label: string }[] ----@alias workspace_ids table - ---@param choice_table InputSelector_choices ----@return InputSelector_choices ----@return workspace_ids -local function get_workspace_elements(choice_table) +---@return InputSelector_choices, workspace_ids +function pub.choices.get_workspace_elements(choice_table) local workspace_ids = {} for _, workspace in ipairs(mux.get_workspace_names()) do table.insert(choice_table, { id = workspace, - label = workspace_formatter(workspace), + label = pub.workspace_formatter(workspace), }) workspace_ids[workspace] = true end @@ -56,9 +56,9 @@ local function get_workspace_elements(choice_table) end ---@param choice_table InputSelector_choices ----@param opts? {extra_args: string?, workspace_ids: workspace_ids} +---@param opts? choice_opts ---@return InputSelector_choices -local function get_zoxide_elements(choice_table, opts) +function pub.choices.get_zoxide_elements(choice_table, opts) if opts == nil then opts = { extra_args = "", workspace_ids = {} } end @@ -77,6 +77,20 @@ local function get_zoxide_elements(choice_table, opts) return choice_table end +---Returns choices for the InputSelector +---@param opts? choice_opts +---@return InputSelector_choices +function pub.get_choices(opts) + if opts == nil then + opts = { extra_args = "" } + end + ---@type InputSelector_choices + local choices = {} + choices, opts.workspace_ids = pub.choices.get_workspace_elements(choices) + choices = pub.choices.get_zoxide_elements(choices, opts) + return choices +end + ---@param workspace string ---@return MuxWindow local function get_current_mux_window(workspace) @@ -88,69 +102,83 @@ local function get_current_mux_window(workspace) error("Could not find a workspace with the name: " .. workspace) end ----@generic T ----@param array T[] ----@param element T +---Check if the workspace exists +---@param workspace string ---@return boolean -local function array_contains(array, element) - for _, value in ipairs(array) do - if element == value then +local function workspace_exists(workspace) + for _, workspace_name in ipairs(mux.get_workspace_names()) do + if workspace == workspace_name then return true end end return false end ----@param extra_args? string +---InputSelector callback when zoxide supplied element is chosen +---@param window MuxWindow +---@param pane Pane +---@param path string +---@param label_path string +local function zoxide_chosen(window, pane, path, label_path) + window:perform_action( + act.SwitchToWorkspace({ + name = label_path, + spawn = { + label = "Workspace: " .. label_path, + cwd = path, + }, + }), + pane + ) + wezterm.emit( + "smart_workspace_switcher.workspace_switcher.created", + get_current_mux_window(label_path), + path, + label_path + ) + -- increment zoxide path score + run_child_process(pub.zoxide_path .. " add " .. path) +end + +---InputSelector callback when workspace element is chosen +---@param window MuxWindow +---@param pane Pane +---@param workspace string +---@param label_workspace string +local function workspace_chosen(window, pane, workspace, label_workspace) + window:perform_action( + act.SwitchToWorkspace({ + name = workspace, + }), + pane + ) + wezterm.emit( + "smart_workspace_switcher.workspace_switcher.chosen", + get_current_mux_window(workspace), + workspace, + label_workspace + ) +end + +---@param opts? choice_opts ---@return action_callback -function pub.switch_workspace(extra_args) +function pub.switch_workspace(opts) return wezterm.action_callback(function(window, pane) wezterm.emit("smart_workspace_switcher.workspace_switcher.start", window) - local opts = { extra_args = extra_args } -- TODO: could be input instead - local choices = {} - choices, opts.workspace_ids = get_workspace_elements(choices) - choices = get_zoxide_elements(choices, opts) + local choices = pub.get_choices(opts) window:perform_action( act.InputSelector({ action = wezterm.action_callback(function(inner_window, inner_pane, id, label) if id and label then wezterm.emit("smart_workspace_switcher.workspace_switcher.selected", window, id, label) - if array_contains(mux.get_workspace_names(), label) then - -- if workspace is choosen - inner_window:perform_action( - act.SwitchToWorkspace({ - name = id, - }), - inner_pane - ) - wezterm.emit( - "smart_workspace_switcher.workspace_switcher.chosen", - get_current_mux_window(id), - id, - label - ) + + if workspace_exists(id) then + -- workspace is choosen + workspace_chosen(inner_window, inner_pane, id, label) else - -- local original_path = string.gsub(label, "^~", wezterm.home_dir) -- TODO: swithced original_path to id - -- if path is choosen - inner_window:perform_action( - act.SwitchToWorkspace({ - name = label, - spawn = { - label = "Workspace: " .. label, - cwd = id, - }, - }), - inner_pane - ) - wezterm.emit( - "smart_workspace_switcher.workspace_switcher.created", - get_current_mux_window(label), - id, - label - ) - -- increment path score - run_child_process(pub.zoxide_path .. " add " .. id) + -- path is choosen + zoxide_chosen(inner_window, inner_pane, id, label) end end end), @@ -163,7 +191,7 @@ function pub.switch_workspace(extra_args) end) end ----sets a default keybind to ALT-s +---sets default keybind to ALT-s ---@param config table function pub.apply_to_config(config) table.insert(config.keys, { @@ -173,9 +201,4 @@ function pub.apply_to_config(config) }) end ----@param formatter fun(label: string): string -function pub.set_workspace_formatter(formatter) - workspace_formatter = formatter -end - return pub From 055cd1c833a5fcbdf98fa93c400aa15600df761d Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:08:33 +0200 Subject: [PATCH 3/6] rename module class to public_module --- plugin/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/init.lua b/plugin/init.lua index 33a1807..5a2159b 100644 --- a/plugin/init.lua +++ b/plugin/init.lua @@ -12,7 +12,7 @@ local is_windows = string.find(wezterm.target_triple, "windows") ~= nil ---@alias choice_opts {extra_args?: string, workspace_ids?: workspace_ids} ---@alias InputSelector_choices { id: string, label: string }[] ----@class module +---@class public_module ---@field zoxide_path string ---@field choices {get_zoxide_elements: (fun(choices: InputSelector_choices, opts: choice_opts?): InputSelector_choices), get_workspace_elements: (fun(choices: InputSelector_choices): (InputSelector_choices, workspace_ids))} ---@field workspace_formatter fun(label: string): string From b36b80fe6e21ab661567273c9589a1994bb57850 Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:13:03 +0200 Subject: [PATCH 4/6] update readme --- README.md | 92 +++++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index df45123..51715d7 100644 --- a/README.md +++ b/README.md @@ -14,118 +14,110 @@ A smart Wezterm workspace switcher inspired by [t-smart-tmux-session-manager](ht ### Setup -1. require the plugin: +1. Require the plugin: ```lua local wezterm = require("wezterm") local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm") ``` -2. Apply default keybinding to config: +2. Apply the default keybinding to the config: ```lua workspace_switcher.apply_to_config(config) ``` -Or make your own keybinding, see [Configuration - Keybinding](#Keybinding) - +Or create your own keybinding, see [Configuration - Keybinding](#Keybinding). ### Configuration: #### Keybinding -Add custom keybinding +To add a custom keybinding: ```lua config.keys = { -- ... -- your other keybindings { - key = "s", - mods = "ALT", - action = workspace_switcher.switch_workspace(), + key = "s", + mods = "ALT", + action = workspace_switcher.switch_workspace(), } } ``` -#### Changing default workspace name +#### Changing the Default Workspace Name +You can set a default workspace name: + ```lua config.default_workspace = "~" ``` -#### Additional filtering - -Users may also choose to include `extra_args` in the call to `switch_workspace`. The string contents of this value are appended to the call to `zoxide query -l`. This can be used to further filter the results of the query. For example, imagine one has a predefined list of projects from which they wish to select. It might be a file, ~/.projects, with contents like: - -``` -/Users/you/projects/gitlab.com/foo/bar -/Users/you/projects/github.com/MLFlexer/smart_workspace_switcher.wezterm -``` +#### Additional Filtering -If you want your project switcher only to select projects from this list, but still make use of the zoxide query ordering, you can call the plugin as: +You can include `extra_args` in the call to `switch_workspace` to filter the results of the zoxide query further. The `extra_args` is just a string concatenated to the command like so: `zoxide query -l `. For example, to select projects from a predefined list in `~/.projects`, call the plugin like this: ```lua - config.keys = { - -- ... - -- your other keybindings - { - key = "s", - mods = "ALT", - action = workspace_switcher.switch_workspace(" | rg -Fxf ~/.projects"), - } - } + workspace_switcher.switch_workspace({ extra_args = " | rg -Fxf ~/.projects" }) ``` -#### Update right-status with the path -Adding the path as a part of the right-status can be done with the `smart_workspace_switcher.workspace_chosen` event which is emitted when choosing the workspace. +#### Updating the Right Status with the Path + +To add the selected path to the right status bar, use the `smart_workspace_switcher.workspace_switcher.chosen` event emitted when choosing a workspace: ```lua - wezterm.on("smart_workspace_switcher.workspace_switcher.chosen", function(window, path) - local base_path = string.gsub(path, "(.*[/\\])(.*)", "%2") + wezterm.on("smart_workspace_switcher.workspace_switcher.chosen", function(window, workspace) + local base_path = string.gsub(workspace, "(.*[/\\])(.*)", "%2") window:set_right_status(wezterm.format({ - { Foreground = { Color = colors.colors.ansi[5] } }, + { Foreground = { Color = "green" } }, { Text = base_path .. " " }, })) end) - wezterm.on("smart_workspace_switcher.workspace_switcher.created", function(window, path) - local base_path = string.gsub(path, "(.*[/\\])(.*)", "%2") + wezterm.on("smart_workspace_switcher.workspace_switcher.created", function(window, workspace) + local base_path = string.gsub(workspace, "(.*[/\\])(.*)", "%2") window:set_right_status(wezterm.format({ - { Foreground = { Color = colors.colors.ansi[5] } }, + { Foreground = { Color = "green" } }, { Text = base_path .. " " }, })) end) - ``` #### Events -Use the events which are emitted when choosing the workspace to add a callback function. The following events are available: -* `smart_workspace_switcher.workspace_switcher.start` - when the fuzzy finder starts -* `smart_workspace_switcher.workspace_switcher.selected` - when a element is selected -* `smart_workspace_switcher.workspace_switcher.created` - after a new workspace is created and switched to upon selecting -* `smart_workspace_switcher.workspace_switcher.chosen` - after switching to a new workspace upon selecting -See example for use below: +The following events are available and can be used to trigger custom behavior: + +* `smart_workspace_switcher.workspace_switcher.start` - Triggered when the fuzzy finder starts. +* `smart_workspace_switcher.workspace_switcher.selected` - Triggered when an element is selected. +* `smart_workspace_switcher.workspace_switcher.created` - Triggered after creating and switching to a new workspace. +* `smart_workspace_switcher.workspace_switcher.chosen` - Triggered after switching to a workspace. + +Example usage: + ```lua - wezterm.on("smart_workspace_switcher.workspace_switcher.chosen", function(window, path) + wezterm.on("smart_workspace_switcher.workspace_switcher.chosen", function(window, workspace) wezterm.log_info("THIS IS EMITTED FROM THE CALLBACK") end) ``` -#### Workspace formatter -Set a custom workspace formatter, see [Wezterm formatting docs](https://wezfurlong.org/wezterm/config/lua/wezterm/format.html) +#### Workspace Formatter + +Set a custom workspace formatter using the following example. For more information, see the [Wezterm formatting docs](https://wezfurlong.org/wezterm/config/lua/wezterm/format.html): ```lua - workspace_switcher.set_workspace_formatter(function(label) + workspace_switcher.workspace_formatter = function(label) return wezterm.format({ { Attribute = { Italic = true } }, { Foreground = { Color = "green" } }, { Background = { Color = "black" } }, { Text = "󱂬: " .. label }, }) - end) + end ``` -#### Zoxide path -Define path to zoxide: + +#### Zoxide Path + +To define a custom path to `zoxide`: ```lua - workspace_switcher.set_zoxide_path("/path/to/zoxide) + workspace_switcher.zoxide_path = "/path/to/zoxide" ``` From 7e39564d367b45d1dca4eb484cd195de29386ea3 Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:46:17 +0200 Subject: [PATCH 5/6] updating readme with how to change elements in the fuzzy finder --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 51715d7..f26fe67 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,25 @@ You can include `extra_args` in the call to `switch_workspace` to filter the res workspace_switcher.switch_workspace({ extra_args = " | rg -Fxf ~/.projects" }) ``` +#### Changing elements of the fuzzy finder + +You can change the list of elements of the fuzzy finder by setting a new function for `get_choices` likes so: + +```lua +local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm") +workspace_switcher.get_choices = function(opts) + -- this will ONLY show the workspace elements, NOT the Zoxide results + return workspace_switcher.choices.get_workspace_elements({}) +end +``` + +By default the function uses the following functions to create a list: + +```lua +workspace_switcher.choices.get_workspace_elements({ id: string, label: string }[]) +workspace_switcher.choices.get_zoxide_elements({ id: string, label: string }[], {extra_args?: string, workspace_ids?: workspace_ids}?) +``` + #### Updating the Right Status with the Path To add the selected path to the right status bar, use the `smart_workspace_switcher.workspace_switcher.chosen` event emitted when choosing a workspace: From 0f15b81ea2706af7d399695d1f802fd3552d3048 Mon Sep 17 00:00:00 2001 From: MLFlexer <75012728+MLFlexer@users.noreply.github.com> Date: Sat, 17 Aug 2024 12:47:03 +0200 Subject: [PATCH 6/6] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f26fe67..82fe9d6 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ You can change the list of elements of the fuzzy finder by setting a new functio local workspace_switcher = wezterm.plugin.require("https://github.com/MLFlexer/smart_workspace_switcher.wezterm") workspace_switcher.get_choices = function(opts) -- this will ONLY show the workspace elements, NOT the Zoxide results - return workspace_switcher.choices.get_workspace_elements({}) + return workspace_switcher.choices.get_workspace_elements({}) end ```