commit db83c3a9e9755a95950b2d89100ac2dad2ab233b Author: MasouShizuka Date: Tue Apr 30 18:23:17 2024 +0800 Initialize the repository. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce3e4fa --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# projects.yazi + +A [Yazi](https://github.com/sxyazi/yazi) plugin that adds the functionality to save and load projects. +A project means all `tabs` and their status, including `cwd` and so on. + +> [!NOTE] +> The latest main branch of Yazi is required at the moment. + +## Features + + - Save/load projects + - Load last project + - Projects persistence + +## Installation + +```sh +# Windows +git clone https://github.com/MasouShizuka/projects.yazi.git %AppData%\yazi\config\plugins\projects.yazi + +# Linux/macOS +git clone https://github.com/MasouShizuka/projects.yazi.git ~/.config/yazi/plugins/projects.yazi +``` + +## Configuration + +Add this to your `keymap.toml`: + +```toml +[[manager.prepend_keymap]] +on = [ "P", "s" ] +run = "plugin projects --args=save" +desc = "Save current project" + +[[manager.prepend_keymap]] +on = [ "P", "l" ] +run = "plugin projects --args=load" +desc = "Load project" + +[[manager.prepend_keymap]] +on = [ "P", "P" ] +run = "plugin projects --args=load_last" +desc = "Load last project" + +[[manager.prepend_keymap]] +on = [ "P", "d" ] +run = "plugin projects --args=delete" +desc = "Delete project" + +[[manager.prepend_keymap]] +on = [ "P", "D" ] +run = "plugin projects --args=delete_all" +desc = "Delete all projects" +``` + +--- + +Additionally there are configurations that can be done using the plugin's `setup` function in Yazi's `init.lua`, i.e. `~/.config/yazi/init.lua`. +The following are the default configurations: + +```lua +require("projects"):setup({ + last = { + update_after_save = true, + update_after_load = true, + }, + notify = { + enable = true, + title = "Projects", + timeout = 3, + level = "info", + }, +}) +``` + +### `last` + +The last project is loaded by `load_last` command. + +When `update_after_save` enabled, the saved project will be saved to last project. +When `update_after_load` enabled, the loaded project will be saved to last project. + +### `notify` + +When enabled, notifications will be shown when the user saves/loads/deletes a project and deletes all projects. + +`title`, `timeout`, `level` are the same as [ya.notify](https://yazi-rs.github.io/docs/plugins/utils/#ya.notify). diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..a01a61f --- /dev/null +++ b/init.lua @@ -0,0 +1,318 @@ +local SUPPORTED_KEYS = { + { on = "0" }, + { on = "1" }, + { on = "2" }, + { on = "3" }, + { on = "4" }, + { on = "5" }, + { on = "6" }, + { on = "7" }, + { on = "8" }, + { on = "9" }, + { on = "A" }, + { on = "B" }, + { on = "C" }, + { on = "D" }, + { on = "E" }, + { on = "F" }, + { on = "G" }, + { on = "H" }, + { on = "I" }, + { on = "J" }, + { on = "K" }, + { on = "L" }, + { on = "M" }, + { on = "N" }, + { on = "O" }, + { on = "P" }, + { on = "Q" }, + { on = "R" }, + { on = "S" }, + { on = "T" }, + { on = "U" }, + { on = "V" }, + { on = "W" }, + { on = "X" }, + { on = "Y" }, + { on = "Z" }, + { on = "a" }, + { on = "b" }, + { on = "c" }, + { on = "d" }, + { on = "e" }, + { on = "f" }, + { on = "g" }, + { on = "h" }, + { on = "i" }, + { on = "j" }, + { on = "k" }, + { on = "l" }, + { on = "m" }, + { on = "n" }, + { on = "o" }, + { on = "p" }, + { on = "q" }, + { on = "r" }, + { on = "s" }, + { on = "t" }, + { on = "u" }, + { on = "v" }, + { on = "w" }, + { on = "x" }, + { on = "y" }, + { on = "z" }, +} + +local _notify = ya.sync(function(state, message) + ya.notify({ + title = state.notify.title, + content = message, + timeout = state.notify.timeout, + level = state.notify.level, + }) +end) + +local _get_default_projects = ya.sync(function(state) + return { + list = {}, + last = nil, + } +end) + +local _save_state = ya.sync(function(state, projects) + state.projects = projects + ps.pub_static(10, "projects", projects) +end) + +local _load_state = ya.sync(function(state) + ps.sub_remote("projects", function(body) + if not state.projects and body then + state.projects = _get_default_projects() + + for _, value in pairs(body.list) do + state.projects.list[#state.projects.list + 1] = value + end + + state.projects.last = body.last + end + end) +end) + +local _get_projects = ya.sync(function(state) + return not state.projects and _get_default_projects() or state.projects +end) + +local _get_real_idx = ya.sync(function(state, idx) + for real_idx, value in pairs(_get_projects().list) do + if value.on == SUPPORTED_KEYS[idx].on then + return tonumber(real_idx) + end + end + + return nil +end) + +local _get_current_project = ya.sync(function(state) + local tabs = cx.tabs + + -- TODO: add more tab properties + local project = { + active_idx = tonumber(tabs.idx), + tabs = {}, + } + + for index, tab in ipairs(tabs) do + project.tabs[#project.tabs + 1] = { + idx = index, + cwd = tostring(tab.current.cwd), + } + end + + return project +end) + +local save_project = ya.sync(function(state, idx, desc) + local projects = _get_projects() + + local real_idx = _get_real_idx(idx) + if not real_idx then + real_idx = #projects.list + 1 + end + + local project = _get_current_project() + projects.list[real_idx] = { + on = SUPPORTED_KEYS[idx].on, + desc = desc, + project = project, + } + + if state.update_after_save then + projects.last = project + end + + _save_state(projects) + + if state.notify.enable then + local message = string.format("Project saved to %s", state.projects.list[real_idx].on) + _notify(message) + end +end) + +local load_project = ya.sync(function(state, project, desc) + -- TODO: add more tab properties to restore + + for _ = 1, #cx.tabs - 1 do + ya.manager_emit("tab_close", { 0 }) + end + + local sorted_tabs = {} + for _, tab in pairs(project.tabs) do + sorted_tabs[tonumber(tab.idx)] = tab + end + for _, tab in pairs(sorted_tabs) do + ya.manager_emit("tab_create", { tab.cwd }) + end + + ya.manager_emit("tab_close", { 0 }) + ya.manager_emit("tab_switch", { project.active_idx - 1 }) + + if state.update_after_load then + local projects = _get_projects() + projects.last = project + _save_state(projects) + end + + if state.notify.enable then + local message + if desc then + message = string.format([["%s" loaded]], desc) + else + message = string.format([[Last project loaded]], desc) + end + _notify(message) + end +end) + +local delete_all_projects = ya.sync(function(state) + _save_state(nil) + + if state.notify.enable then + local message = "All projects deleted" + _notify(message) + end +end) + +local delete_project = ya.sync(function(state, idx) + local projects = _get_projects() + + local message = string.format([["%s" deleted]], tostring(projects.list[idx].desc)) + + table.remove(projects.list, idx) + _save_state(projects) + + if state.notify.enable then + _notify(message) + end +end) + +return { + entry = function(_, args) + local action = args[1] + if not action then + return + end + + if action == "save" then + local idx = ya.which({ cands = SUPPORTED_KEYS, silent = false }) + if not idx then + return + end + + local default_desc = string.format("Project %s", SUPPORTED_KEYS[idx].on) + local value, event = ya.input({ + title = "Project name:", + value = default_desc, + position = { "center", w = 40 }, + }) + if event ~= 1 then + return + end + + local desc + if value ~= "" then + desc = value + else + desc = default_desc + end + + save_project(idx, desc) + return + end + + if action == "delete_all" then + delete_all_projects() + return + end + + local projects = _get_projects() + + if action == "load_last" then + local last_project = projects.last + if last_project then + load_project(last_project) + end + return + end + + local list = projects.list + + local selected_idx = ya.which({ cands = list }) + if not selected_idx then + return + end + + if action == "load" then + local selected = list[selected_idx] + load_project(selected.project, selected.desc) + elseif action == "delete" then + delete_project(selected_idx) + end + end, + setup = function(state, args) + state.last = { + update_after_save = true, + update_after_load = true, + } + if type(args.last) == "table" then + if type(args.last.update_after_save) == "boolean" then + state.last.update_after_save = args.last.update_after_save + end + if type(args.last.update_after_load) == "boolean" then + state.last.update_after_load = args.last.update_after_load + end + end + + state.notify = { + enable = true, + title = "Projects", + timeout = 3, + level = "info", + } + if type(args.notify) == "table" then + if type(args.notify.enable) == "boolean" then + state.notify.enable = args.notify.enable + end + if type(args.notify.title) == "string" then + state.notify.title = args.notify.title + end + if type(args.notify.timeout) == "number" then + state.notify.timeout = args.notify.timeout + end + if type(args.notify.level) == "string" then + state.notify.level = args.notify.level + end + end + + _load_state() + end, +}