15 changed files with 556 additions and 191 deletions
@ -1,145 +0,0 @@ |
|||||||
-- Terminal helpers layered on top of the native LazyVim/Snacks terminal. |
|
||||||
-- |
|
||||||
-- Native behaviour is kept intact and untouched: |
|
||||||
-- <C-/> / <C-_> toggle a bottom terminal at the project root (supports a |
|
||||||
-- count, e.g. 2<C-/> opens a *separate* terminal #2) |
|
||||||
-- <leader>fT terminal in the current working directory |
|
||||||
-- |
|
||||||
-- This module adds (actual key bindings are declared in lua/plugins/terminal.lua): |
|
||||||
-- open_float() open/focus a *dedicated* floating terminal (close it with <C-/>) |
|
||||||
-- picker() list every live terminal so any contextual terminal can be |
|
||||||
-- focused or restored even after its window was closed/hidden. |
|
||||||
local M = {} |
|
||||||
|
|
||||||
-- The floating terminal. Tracked as an upvalue so the same key always re-focuses |
|
||||||
-- the *same* float regardless of which directory the editor is in. |
|
||||||
---@type snacks.win? |
|
||||||
local float |
|
||||||
|
|
||||||
--- Open or focus the floating terminal, or hide it if already focused. |
|
||||||
-- NOTE: currently unused; the binding calls open_float() instead so shell |
|
||||||
-- tab-completion keeps working while the terminal is open. Kept around for a |
|
||||||
-- future explicit open/close toggle. |
|
||||||
function M.toggle_float() |
|
||||||
-- Reuse the existing instance: hide if visible, re-show if hidden. |
|
||||||
if float and float:buf_valid() then |
|
||||||
if float:win_valid() then |
|
||||||
float:hide() |
|
||||||
else |
|
||||||
float:show() |
|
||||||
vim.cmd("startinsert") |
|
||||||
end |
|
||||||
return |
|
||||||
end |
|
||||||
|
|
||||||
-- First open: a high fixed count guarantees this terminal never collides with |
|
||||||
-- the count-keyed bottom terminals (1, 2, 3 ...), so it is always the *same* |
|
||||||
-- float for <C-Tab> no matter the context. |
|
||||||
float = Snacks.terminal(nil, { |
|
||||||
count = 99, |
|
||||||
win = { position = "float", border = "rounded" }, |
|
||||||
}) |
|
||||||
|
|
||||||
-- Close it with a single keystroke from inside the shell: hides the |
|
||||||
-- window (buffer-local, so it only affects this floating terminal and never |
|
||||||
-- the bottom split, where shell tab-completion must keep working). |
|
||||||
if float and float.buf then |
|
||||||
vim.keymap.set("t", "<leader>tt", function() |
|
||||||
if float and float:win_valid() then |
|
||||||
float:hide() |
|
||||||
end |
|
||||||
end, { buffer = float.buf, silent = true, desc = "Hide floating terminal" }) |
|
||||||
end |
|
||||||
end |
|
||||||
|
|
||||||
--- Open or focus the floating terminal. This never hides the window, so |
|
||||||
--- once the float is open the key falls through to the shell and stays available |
|
||||||
--- for tab-completion. Close the floating terminal with <C-/> while it is |
|
||||||
--- visible (handled by Snacks' native terminal keymap). |
|
||||||
function M.open_float() |
|
||||||
if float and float:buf_valid() then |
|
||||||
if not float:win_valid() then |
|
||||||
float:show() |
|
||||||
end |
|
||||||
if float:win_valid() then |
|
||||||
vim.api.nvim_set_current_win(float.win) |
|
||||||
end |
|
||||||
vim.cmd("startinsert") |
|
||||||
return |
|
||||||
end |
|
||||||
|
|
||||||
-- First open: a high fixed count guarantees this terminal never collides with |
|
||||||
-- the count-keyed bottom terminals (1, 2, 3 ...), so it is always the *same* |
|
||||||
-- float for <C-Tab> no matter the context. |
|
||||||
float = Snacks.terminal(nil, { |
|
||||||
count = 99, |
|
||||||
win = { position = "float", border = "rounded" }, |
|
||||||
}) |
|
||||||
end |
|
||||||
|
|
||||||
--- List every live Snacks terminal in a picker. Restore/focus one with <CR>, |
|
||||||
--- kill one with <d>. |
|
||||||
function M.picker() |
|
||||||
local terms = Snacks.terminal.list() |
|
||||||
if #terms == 0 then |
|
||||||
Snacks.notify.info("No open terminals") |
|
||||||
return |
|
||||||
end |
|
||||||
|
|
||||||
local items = {} ---@type snacks.picker.finder.Item[] |
|
||||||
for _, term in ipairs(terms) do |
|
||||||
local meta = vim.b[term.buf] and vim.b[term.buf].snacks_terminal or {} |
|
||||||
items[#items + 1] = { |
|
||||||
term = term, |
|
||||||
buf = term.buf, |
|
||||||
id = meta.id, |
|
||||||
cwd = meta.cwd or "?", |
|
||||||
cmd = meta.cmd or vim.o.shell, |
|
||||||
visible = term:win_valid(), |
|
||||||
text = ("#%s %s %s"):format(meta.id or "?", meta.cwd or "?", meta.cmd or vim.o.shell), |
|
||||||
} |
|
||||||
end |
|
||||||
|
|
||||||
Snacks.picker({ |
|
||||||
title = "Terminals", |
|
||||||
items = items, |
|
||||||
format = function(item) |
|
||||||
local ret = {} ---@type snacks.picker.Highlight[] |
|
||||||
ret[#ret + 1] = { |
|
||||||
item.visible and "● " or "○ ", |
|
||||||
item.visible and "SnacksPickerSpecial" or "SnacksPickerComment", |
|
||||||
} |
|
||||||
ret[#ret + 1] = { ("#%-2s"):format(item.id or "?"), "Number" } |
|
||||||
ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cwd, ":~"), "SnacksPickerPath" } |
|
||||||
ret[#ret + 1] = { " " .. vim.fn.fnamemodify(item.cmd, ":t"), "SnacksPickerComment" } |
|
||||||
return ret |
|
||||||
end, |
|
||||||
confirm = function(picker, item) |
|
||||||
picker:close() |
|
||||||
local term = item.term |
|
||||||
if not term:buf_valid() then |
|
||||||
Snacks.notify.warn("Terminal no longer exists") |
|
||||||
return |
|
||||||
end |
|
||||||
if term:win_valid() then |
|
||||||
vim.api.nvim_set_current_win(term.win) |
|
||||||
else |
|
||||||
term:show() |
|
||||||
vim.cmd("startinsert") |
|
||||||
end |
|
||||||
end, |
|
||||||
win = { input = { keys = { ["d"] = "term_delete" } } }, |
|
||||||
actions = { |
|
||||||
term_delete = function(picker, item) |
|
||||||
local term = item.term |
|
||||||
picker:close() |
|
||||||
if term and term:buf_valid() then |
|
||||||
term:close() |
|
||||||
pcall(vim.api.nvim_buf_delete, term.buf, { force = true }) |
|
||||||
end |
|
||||||
end, |
|
||||||
}, |
|
||||||
}) |
|
||||||
end |
|
||||||
|
|
||||||
return M |
|
||||||
@ -0,0 +1,11 @@ |
|||||||
|
return { |
||||||
|
{ |
||||||
|
"chodak166/clangd-container.nvim", |
||||||
|
-- dir = vim.fn.expand("~/src/git/clangd-container.nvim"), |
||||||
|
dependencies = { "p00f/clangd_extensions.nvim" }, |
||||||
|
opts = {}, |
||||||
|
keys = { |
||||||
|
{ "<leader>cC", "<cmd>ClangdContainer<cr>", desc = "Clangd Container" }, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
return { |
||||||
|
{ |
||||||
|
"https://codeberg.org/esensar/nvim-dev-container", |
||||||
|
dependencies = "nvim-treesitter/nvim-treesitter", |
||||||
|
config = function() |
||||||
|
require("devcontainer").setup({ |
||||||
|
-- Your options go here |
||||||
|
-- e.g., auto_start = true, icons = { ... } |
||||||
|
container_runtime = docker, |
||||||
|
}) |
||||||
|
end, |
||||||
|
}, |
||||||
|
} |
||||||
@ -1,23 +1,11 @@ |
|||||||
-- Terminal UX layered on Snacks (replaces toggleterm). |
-- Terminal UX layered on Snacks (replaces toggleterm). |
||||||
-- Helpers live in lua/config/terminal.lua; keys are declared here so lazy.nvim |
-- All keymaps (<leader>tt, <leader>ft, <C-/> with 99-count float) are |
||||||
-- registers the which-key labels with the rest of the snacks spec. |
-- registered by the plugin's setup(). |
||||||
return { |
return { |
||||||
"folke/snacks.nvim", |
"chodak166/snacks-terminal-ctl.nvim", |
||||||
keys = { |
-- dir = "~/src/git/snacks-terminal-ctl.nvim", |
||||||
{ |
name = "snacks-terminal-ctl.nvim", |
||||||
"<leader>tt", |
dependencies = { "folke/snacks.nvim" }, |
||||||
function() |
event = "VeryLazy", |
||||||
require("config.terminal").open_float() |
opts = {}, |
||||||
end, |
|
||||||
desc = "Terminal (float)", |
|
||||||
mode = "n", |
|
||||||
}, |
|
||||||
{ |
|
||||||
"<leader>ft", |
|
||||||
function() |
|
||||||
require("config.terminal").picker() |
|
||||||
end, |
|
||||||
desc = "Terminals (list/restore)", |
|
||||||
}, |
|
||||||
}, |
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,135 @@ |
|||||||
|
local M = {} |
||||||
|
|
||||||
|
local function get_yanked_info() |
||||||
|
return ya.sync(function() |
||||||
|
local cwd = cx.active.current.cwd |
||||||
|
local cut = cx.yanked.is_cut |
||||||
|
|
||||||
|
local yanked = {} |
||||||
|
for _, f in pairs(cx.yanked) do |
||||||
|
yanked[#yanked + 1] = { url = f.url, name = tostring(f.url:name()) } |
||||||
|
end |
||||||
|
|
||||||
|
return cwd, cut, yanked |
||||||
|
end) |
||||||
|
end |
||||||
|
|
||||||
|
local function rename_default(name) |
||||||
|
local suffix = os.date("-%y%m%d") |
||||||
|
local stem = name:match("^(.+)%..-$") or name |
||||||
|
local ext = name:match("%.(.+)$") or "" |
||||||
|
if ext ~= "" then |
||||||
|
return stem .. suffix .. "." .. ext |
||||||
|
else |
||||||
|
return name .. suffix |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
function M:entry(_, job) |
||||||
|
local cwd, cut, yanked = get_yanked_info() |
||||||
|
if #yanked == 0 then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local follow = job.args.follow or false |
||||||
|
local resolved = {} |
||||||
|
|
||||||
|
-- Partition: check each yanked item for conflicts |
||||||
|
local conflicts = {} |
||||||
|
for _, item in ipairs(yanked) do |
||||||
|
local dest = cwd:join(item.name) |
||||||
|
local cha = fs.cha(dest) |
||||||
|
if cha ~= nil then |
||||||
|
conflicts[#conflicts + 1] = { url = item.url, dest = dest, name = item.name } |
||||||
|
else |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(dest), |
||||||
|
overwrite = false, |
||||||
|
} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Process conflicts |
||||||
|
local sync_all = false |
||||||
|
for _, item in ipairs(conflicts) do |
||||||
|
if sync_all then |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
} |
||||||
|
else |
||||||
|
local choice = ya.pick({ |
||||||
|
title = "Paste conflict: " .. item.name, |
||||||
|
items = { |
||||||
|
"Sync (merge/overwrite)", |
||||||
|
"Sync all remaining", |
||||||
|
"Rename", |
||||||
|
"Skip", |
||||||
|
"Cancel", |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
-- ya.pick returns 0-based index or nil |
||||||
|
if choice == nil or choice == 4 then |
||||||
|
-- Cancel: paste what we have so far, then abort |
||||||
|
if #resolved > 0 then |
||||||
|
ya.emit("paste_resolved", { |
||||||
|
cut = cut, |
||||||
|
follow = follow, |
||||||
|
items = resolved, |
||||||
|
}) |
||||||
|
end |
||||||
|
return |
||||||
|
|
||||||
|
elseif choice == 0 then |
||||||
|
-- Sync current |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
} |
||||||
|
|
||||||
|
elseif choice == 1 then |
||||||
|
-- Sync all remaining |
||||||
|
sync_all = true |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
} |
||||||
|
|
||||||
|
elseif choice == 2 then |
||||||
|
-- Rename |
||||||
|
local default = rename_default(item.name) |
||||||
|
local value, event = ya.input({ |
||||||
|
title = "New name", |
||||||
|
value = default, |
||||||
|
}) |
||||||
|
if event == 1 then |
||||||
|
local new_dest = cwd:join(value) |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(new_dest), |
||||||
|
overwrite = false, |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
elseif choice == 3 then |
||||||
|
-- Skip: do nothing |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Dispatch all resolved items |
||||||
|
if #resolved > 0 then |
||||||
|
ya.emit("paste_resolved", { |
||||||
|
cut = cut, |
||||||
|
follow = follow, |
||||||
|
items = resolved, |
||||||
|
}) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
return M |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
MIT License |
||||||
|
|
||||||
|
Copyright (c) 2026 chodak166 |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
@ -0,0 +1,220 @@ |
|||||||
|
local M = {} |
||||||
|
|
||||||
|
local get_yanked_info = ya.sync(function() |
||||||
|
local cwd = cx.active.current.cwd |
||||||
|
local cut = cx.yanked.is_cut |
||||||
|
|
||||||
|
local yanked = {} |
||||||
|
for _, url in ipairs(cx.yanked:urls()) do |
||||||
|
yanked[#yanked + 1] = { url = url, name = tostring(url.name) } |
||||||
|
end |
||||||
|
|
||||||
|
return { cwd = cwd, cut = cut, yanked = yanked } |
||||||
|
end) |
||||||
|
|
||||||
|
local function rename_default(name, attempt) |
||||||
|
if attempt and attempt > 1 then |
||||||
|
local suffix = string.format("-%d", attempt) |
||||||
|
local stem = name:match("^(.+)%..-$") or name |
||||||
|
local ext = name:match("%.(.+)$") or "" |
||||||
|
if ext ~= "" then |
||||||
|
return stem .. suffix .. "." .. ext |
||||||
|
else |
||||||
|
return name .. suffix |
||||||
|
end |
||||||
|
end |
||||||
|
local suffix = os.date("-%y%m%d") |
||||||
|
local stem = name:match("^(.+)%..-$") or name |
||||||
|
local ext = name:match("%.(.+)$") or "" |
||||||
|
if ext ~= "" then |
||||||
|
return stem .. suffix .. "." .. ext |
||||||
|
else |
||||||
|
return name .. suffix |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
--Choices: |
||||||
|
-- 0: Merge — merge directory contents, overwrite individual files |
||||||
|
-- 1: Merge all — same, applied to all remaining conflicts |
||||||
|
-- 2: Replace — replace destination entirely (delete then copy) |
||||||
|
-- 3: Rename — prompt for a new name |
||||||
|
-- 4: Skip — skip this item |
||||||
|
-- 5: Cancel(nil) — abort |
||||||
|
local function pick_conflict(name) |
||||||
|
return ya.pick({ |
||||||
|
title = "Paste conflict: " .. name, |
||||||
|
items = { |
||||||
|
"Merge", |
||||||
|
"Merge all remaining", |
||||||
|
"Replace", |
||||||
|
"Rename", |
||||||
|
"Skip", |
||||||
|
"Cancel", |
||||||
|
}, |
||||||
|
}) |
||||||
|
end |
||||||
|
|
||||||
|
function M:entry(job) |
||||||
|
local info = get_yanked_info() |
||||||
|
local cwd, cut, yanked = info.cwd, info.cut, info.yanked |
||||||
|
|
||||||
|
if #yanked == 0 then |
||||||
|
return |
||||||
|
end |
||||||
|
|
||||||
|
local follow = job.args.follow or false |
||||||
|
local resolved = {} |
||||||
|
|
||||||
|
-- Partition: check each yanked item for conflicts |
||||||
|
local conflicts = {} |
||||||
|
for _, item in ipairs(yanked) do |
||||||
|
local dest = cwd:join(item.name) |
||||||
|
local cha = fs.cha(dest) |
||||||
|
if cha ~= nil then |
||||||
|
conflicts[#conflicts + 1] = { url = item.url, dest = dest, name = item.name } |
||||||
|
else |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(dest), |
||||||
|
overwrite = false, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Dispatch helper |
||||||
|
local function dispatch(items) |
||||||
|
if #items > 0 then |
||||||
|
ya.emit("paste_resolved", { |
||||||
|
cut = cut, |
||||||
|
follow = follow, |
||||||
|
items = items, |
||||||
|
}) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Process conflicts |
||||||
|
local merge_all = false |
||||||
|
for _, item in ipairs(conflicts) do |
||||||
|
if merge_all then |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
else |
||||||
|
local choice = pick_conflict(item.name) |
||||||
|
|
||||||
|
if choice == nil or choice == 5 then |
||||||
|
-- Cancel: paste what we have so far, then abort |
||||||
|
dispatch(resolved) |
||||||
|
return |
||||||
|
|
||||||
|
elseif choice == 0 then |
||||||
|
-- Merge current |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
|
||||||
|
elseif choice == 1 then |
||||||
|
-- Merge all remaining |
||||||
|
merge_all = true |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
|
||||||
|
elseif choice == 2 then |
||||||
|
-- Replace |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(item.dest), |
||||||
|
overwrite = true, |
||||||
|
replace = true, |
||||||
|
} |
||||||
|
|
||||||
|
elseif choice == 3 then |
||||||
|
-- Rename: loop until a non-conflicting name is chosen or cancelled |
||||||
|
local attempt = 0 |
||||||
|
while true do |
||||||
|
attempt = attempt + 1 |
||||||
|
local default = rename_default(item.name, attempt) |
||||||
|
local value, event = ya.input({ |
||||||
|
title = "New name", |
||||||
|
value = default, |
||||||
|
pos = { "center", w = 60 }, |
||||||
|
}) |
||||||
|
if event ~= 1 then |
||||||
|
-- User cancelled the input — skip this item |
||||||
|
break |
||||||
|
end |
||||||
|
|
||||||
|
local new_dest = cwd:join(value) |
||||||
|
if fs.cha(new_dest) == nil then |
||||||
|
-- No conflict — add and continue |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(new_dest), |
||||||
|
overwrite = false, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
break |
||||||
|
else |
||||||
|
-- New name also conflicts — show the conflict dialog again |
||||||
|
local sub_choice = pick_conflict(value) |
||||||
|
if sub_choice == nil or sub_choice == 5 then |
||||||
|
-- Cancel everything |
||||||
|
dispatch(resolved) |
||||||
|
return |
||||||
|
elseif sub_choice == 0 then |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(new_dest), |
||||||
|
overwrite = true, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
break |
||||||
|
elseif sub_choice == 1 then |
||||||
|
merge_all = true |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(new_dest), |
||||||
|
overwrite = true, |
||||||
|
replace = false, |
||||||
|
} |
||||||
|
break |
||||||
|
elseif sub_choice == 2 then |
||||||
|
resolved[#resolved + 1] = { |
||||||
|
from = tostring(item.url), |
||||||
|
to = tostring(new_dest), |
||||||
|
overwrite = true, |
||||||
|
replace = true, |
||||||
|
} |
||||||
|
break |
||||||
|
elseif sub_choice == 3 then |
||||||
|
-- Rename again — increment attempt for different default |
||||||
|
-- (loop continues) |
||||||
|
elseif sub_choice == 4 then |
||||||
|
-- Skip |
||||||
|
break |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
elseif choice == 4 then |
||||||
|
-- Skip: do nothing |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
-- Dispatch all resolved items |
||||||
|
dispatch(resolved) |
||||||
|
end |
||||||
|
|
||||||
|
return M |
||||||
Loading…
Reference in new issue