Compare commits

...

6 Commits

  1. 10
      .config/i3/config
  2. 4
      .config/kitty/current-theme.conf
  3. 19
      .config/nvim/lazy-lock.json
  4. 15
      .config/nvim/lua/config/keymaps.lua
  5. 3
      .config/nvim/lua/config/options.lua
  6. 145
      .config/nvim/lua/config/terminal.lua
  7. 11
      .config/nvim/lua/plugins/clangd-container.lua
  8. 13
      .config/nvim/lua/plugins/devcontainers.lua
  9. 28
      .config/nvim/lua/plugins/terminal.lua
  10. 20
      .config/sway/config
  11. 5
      .config/yazi/keymap.toml
  12. 5
      .config/yazi/package.toml
  13. 135
      .config/yazi/plugins/paste-dialog.lua
  14. 21
      .config/yazi/plugins/paste-dialog.yazi/LICENSE
  15. 115
      .config/yazi/plugins/paste-dialog.yazi/README.md
  16. 220
      .config/yazi/plugins/paste-dialog.yazi/main.lua
  17. 2
      .gitignore
  18. 15
      .zshrc

10
.config/i3/config

@ -33,12 +33,12 @@ set $workspace1 1: >_
set $workspace2 2: dev
set $workspace3 3: www
set $workspace4 4: files
set $workspace5 5: e-mail
set $workspace6 6: IM
set $workspace7 7: video
set $workspace5 5: gitlab
set $workspace6 6: virt-manager
set $workspace7 7: notes
set $workspace8 8: workspace
set $workspace9 9: workspace
set $workspace10 10: Music
set $workspace10 10: remotes
set $workspace11 V: VM
# switch to workspace
@ -471,7 +471,7 @@ bar {
#Use tray_output primary/none/<output name> to define where the tray should be (multiple tray_output can be used)
#Remember to set any of your displays to primary (i.e. using xrandr)
#tray_output primary
tray_output $rightOutput
tray_output $leftOutput
colors {
separator $clFocusedActiveText
#background $clUnfocusedBackground

4
.config/kitty/current-theme.conf

@ -6,8 +6,8 @@ color0 #383838
color8 #474747
color1 #a95551
color9 #a97775
color2 #666666
color10 #8c8c8c
color2 #6b8f4e
color10 #8fb86a
color3 #a98051
color11 #a99175
color4 #657d3e

19
.config/nvim/lazy-lock.json

@ -3,7 +3,8 @@
"avante.nvim": { "branch": "main", "commit": "90a0e77c63251c367935caeaa0070670522c960b" },
"blink.cmp": { "branch": "main", "commit": "78336bc89ee5365633bcf754d93df01678b5c08f" },
"bufferline.nvim": { "branch": "main", "commit": "655133c3b4c3e5e05ec549b9f8cc2894ac6f51b3" },
"catppuccin": { "branch": "main", "commit": "05e8787020dcfdb937bf2ff23855ea2415b4e072" },
"catppuccin": { "branch": "main", "commit": "c7c692a0ad3080710893abbae100171819a3e4be" },
"clangd-container.nvim": { "branch": "master", "commit": "8bfb4f202ea64710ce46d8790ca95c9ee89b6ce6" },
"clangd_extensions.nvim": { "branch": "main", "commit": "78c2ecd659d54972be17aa6ba2deac3c53223b80" },
"conform.nvim": { "branch": "master", "commit": "619363c30309d29ffa631e67c8183f2a72caa373" },
"diffview.nvim": { "branch": "main", "commit": "4516612fe98ff56ae0415a259ff6361a89419b0a" },
@ -11,7 +12,7 @@
"flash.nvim": { "branch": "main", "commit": "b6346946d10d07998efee029fb0f7a593806d0cd" },
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
"gitsigns.nvim": { "branch": "main", "commit": "31d6fb2d618bca1482b9f274751ead5f03461408" },
"grug-far.nvim": { "branch": "main", "commit": "1cc080f55706b38aabfa97d40acb6adf59ac4a5a" },
"grug-far.nvim": { "branch": "main", "commit": "0f6a5d0d7a70659b11bbcd6b3673ee0fe61b6024" },
"hybrid-theme": { "branch": "main", "commit": "626ae5426a7c8f21dfccce286c690d9cf4195b46" },
"image.nvim": { "branch": "master", "commit": "88351f1f7d9dbae286e671ce3690a49660dd8a5c" },
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
@ -19,21 +20,23 @@
"lazy.nvim": { "branch": "main", "commit": "85c7ff3711b730b4030d03144f6db6375044ae82" },
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
"lualine.nvim": { "branch": "master", "commit": "221ce6b2d999187044529f49da6554a92f740a96" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "a4068c3ebd4cb335b25239e5ea35c22ee6007962" },
"mason-lspconfig.nvim": { "branch": "main", "commit": "7adc933dabcc7c86ae6b07aff7ee68eac398491f" },
"mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" },
"mini.ai": { "branch": "main", "commit": "25248c6aa002391936a6200f12d1466015987133" },
"mini.files": { "branch": "main", "commit": "d08e8da87be8bf07d09245294821706afcca31cd" },
"mini.files": { "branch": "main", "commit": "fe78cd12e5501a7a56413096c20167568ec2ac8e" },
"mini.icons": { "branch": "main", "commit": "98faae31e9be1cc054ae63485e58ceb185efcad0" },
"mini.pairs": { "branch": "main", "commit": "b1fd9df3bb4a41c8f45778a3859ee80ef6b367e3" },
"mini.pairs": { "branch": "main", "commit": "b1c5a726921b7a8c9321e9a7a208aa0571de5810" },
"noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" },
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
"nvim-dev-container": { "branch": "main", "commit": "365b4c815339d42133829006dce3e5f8b182b771" },
"nvim-lint": { "branch": "master", "commit": "a219b2c9e5b4765e5c845aba119dad55806fcaf1" },
"nvim-lspconfig": { "branch": "master", "commit": "d5b6e3db4c17b0146f63a2fc47e2027a754b2cb1" },
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
"nvim-treesitter-textobjects": { "branch": "main", "commit": "851e865342e5a4cb1ae23d31caf6e991e1c99f1e" },
"nvim-lspconfig": { "branch": "master", "commit": "d592c1e6ad9a0a01b3d5ed3f0345d68407167181" },
"nvim-treesitter": { "branch": "main", "commit": "bd41519ff7901da11ba6e1c8f419f17f2d94305a" },
"nvim-treesitter-textobjects": { "branch": "main", "commit": "898ee307df58f854d11cd7edd06472574d48014e" },
"nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" },
"persistence.nvim": { "branch": "main", "commit": "b20b2a7887bd39c1a356980b45e03250f3dce49c" },
"plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" },
"snacks-terminal-ctl.nvim": { "branch": "master", "commit": "97727421c8d0ffa33d893f22e38cda93e9010742" },
"snacks.nvim": { "branch": "main", "commit": "882c996cf28183f4d63640de0b4c02ec886d01f2" },
"todo-comments.nvim": { "branch": "main", "commit": "31e3c38ce9b29781e4422fc0322eb0a21f4e8668" },
"tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" },

15
.config/nvim/lua/config/keymaps.lua

@ -2,16 +2,5 @@
-- Default keymaps that are always set: https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/config/keymaps.lua
-- Add any additional keymaps here
-- Override LazyVim's <C-/> / <C-_>: count 99 opens the floating terminal,
-- everything else stays native (bottom terminal at project root).
-- Both keys are mapped because terminals differ in which code Ctrl+/ sends.
local function term_open()
if vim.v.count == 99 then
require("config.terminal").open_float()
else
Snacks.terminal.focus(nil, { cwd = LazyVim.root() })
end
end
vim.keymap.set({ "n", "t" }, "<C-/>", term_open, { desc = "Terminal (Root Dir) | 99<C-/> = float" })
vim.keymap.set({ "n", "t" }, "<C-_>", term_open, { desc = "Terminal (Root Dir) | 99<C-/> = float" })
-- Terminal keymaps (<C-/> toggle with 99-count float, <leader>tt, <leader>ft)
-- are registered by the snacks-terminal-ctl plugin (lua/plugins/terminal.lua).

3
.config/nvim/lua/config/options.lua

@ -25,6 +25,9 @@ vim.g.lazyvim_no_inlay_hints = true
-- -- Enable list mode to show whitespace
-- vim.opt.list = true
-- disable autoformatting not to generate large diffs:
vim.g.autoformat = false
-- Configure listchars to use dots (·) for spaces and tabs
vim.opt.listchars = {
tab = "· ", -- Dot for the first part of a tab, followed by space

145
.config/nvim/lua/config/terminal.lua

@ -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

11
.config/nvim/lua/plugins/clangd-container.lua

@ -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" },
},
},
}

13
.config/nvim/lua/plugins/devcontainers.lua

@ -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,
},
}

28
.config/nvim/lua/plugins/terminal.lua

@ -1,23 +1,11 @@
-- Terminal UX layered on Snacks (replaces toggleterm).
-- Helpers live in lua/config/terminal.lua; keys are declared here so lazy.nvim
-- registers the which-key labels with the rest of the snacks spec.
-- All keymaps (<leader>tt, <leader>ft, <C-/> with 99-count float) are
-- registered by the plugin's setup().
return {
"folke/snacks.nvim",
keys = {
{
"<leader>tt",
function()
require("config.terminal").open_float()
end,
desc = "Terminal (float)",
mode = "n",
},
{
"<leader>ft",
function()
require("config.terminal").picker()
end,
desc = "Terminals (list/restore)",
},
},
"chodak166/snacks-terminal-ctl.nvim",
-- dir = "~/src/git/snacks-terminal-ctl.nvim",
name = "snacks-terminal-ctl.nvim",
dependencies = { "folke/snacks.nvim" },
event = "VeryLazy",
opts = {},
}

20
.config/sway/config

@ -27,16 +27,16 @@ set $configDir ~/.config/sway
# Workspaces
#################################################################
set $workspace1 1: >_
set $workspace2 2: dev
set $workspace3 3: www
set $workspace4 4: files
set $workspace5 5: e-mail
set $workspace6 6: IM
set $workspace7 7: video
set $workspace8 8: workspace
set $workspace9 9: workspace
set $workspace10 10: Music
set $workspace1 1: 1: >_
set $workspace2 2: 2: dev
set $workspace3 3: 3: www
set $workspace4 4: 4: files
set $workspace5 5: 5: e-mail
set $workspace6 6: 6: IM
set $workspace7 7: 7: video
set $workspace8 8: 8: workspace
set $workspace9 9: 9: workspace
set $workspace10 10: 10: Music
set $workspace11 V: VM
# Switch to workspace

5
.config/yazi/keymap.toml

@ -54,3 +54,8 @@ on = "<C-d>"
run = "plugin diff"
desc = "Diff the selected with the hovered file"
[[mgr.prepend_keymap]]
on = "p"
run = "plugin paste-dialog"
desc = "Paste with conflict dialog"

5
.config/yazi/package.toml

@ -58,6 +58,11 @@ use = "yazi-rs/plugins:jump-to-char"
rev = "598cdb6"
hash = "7a4b4237223aaa94e589d1c01a23542a"
[[plugin.deps]]
use = "chodak166/paste-dialog"
rev = "645b32a"
hash = "357d322e81ff7449c0249ae17b55aaa5"
[[flavor.deps]]
use = "Chromium-3-Oxide/everforest-medium"
rev = "e1ead7b"

135
.config/yazi/plugins/paste-dialog.lua

@ -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

21
.config/yazi/plugins/paste-dialog.yazi/LICENSE

@ -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.

115
.config/yazi/plugins/paste-dialog.yazi/README.md

@ -0,0 +1,115 @@
# paste-dialog.yazi
A [yazi](https://github.com/sxyazi/yazi) plugin that shows an interactive
conflict-resolution dialog when pasting files. Instead of silently appending
`_1`, `_2` suffixes to conflicting names, you choose how to handle each
conflict individually.
| Option | Action | Description |
|--------|--------|-------------|
| **Merge** | merge / overwrite | Merge directory contents recursively; overwrite individual files. _(default — press Enter)_ |
| **Merge all remaining** | merge / overwrite | Same as Merge, but applied to every remaining conflict without prompting again. |
| **Replace** | replace | Delete the destination entirely, then copy fresh (removes files that exist only at the destination). |
| **Rename** | rename | Prompt for a new name, pre-filled with `name-yymmdd`. If the new name also conflicts the dialog reappears with a different default (`name-2`, `name-3`, …). |
| **Skip** | skip | Leave this item untouched. |
| **Cancel** | abort | Paste any non-conflicting items already queued, then stop. _(Esc)_ |
Non-conflicting items always paste silently — the dialog only appears when a
conflict is detected.
> ⚠ **This plugin requires a modified version of yazi.**
> See [Requirements](#requirements) below.
---
## Requirements
This plugin depends on Rust-level changes to yazi that have **not** been merged
into upstream:
| Change | Why it's needed |
|--------|----------------|
| `paste_resolved` actor + parser | Per-item paste with explicit `from`/`to`/`overwrite`/`replace` — not possible with the existing `ya.emit("paste")`. |
| `file_copy_one` / `file_cut_one` | Scheduler methods that accept individual source/destination URLs with a `replace` flag. |
| `ya.pick({ title=, items= })` | Exposes the existing pick dialog to Lua plugins (was internal-only). |
| `PickCfg::with_title()` | Lets `ya.pick()` override the dialog title. |
Build yazi from the fork:
```sh
git clone https://github.com/chodak166/yazi.git
cd yazi
cargo build --release
# Install the modified binaries on your PATH
cp target/release/yazi target/release/ya ~/.local/bin/
```
---
## Installation
### Option A — install via `ya pkg` (recommended)
```sh
ya pkg add chodak166/paste-dialog.yazi
```
### Option B — manual install
```sh
# 1. Create the plugin directory
mkdir -p ~/.config/yazi/plugins/paste-dialog.yazi
# 2. Download main.lua into it
curl -fsSL https://raw.githubusercontent.com/chodak166/paste-dialog.yazi/main/main.lua \
-o ~/.config/yazi/plugins/paste-dialog.yazi/main.lua
```
### Add the keybinding
Add the following to `~/.config/yazi/keymap.toml` (the `prepend_keymap` keeps
all default bindings intact):
```toml
[[mgr.prepend_keymap]]
on = "p"
run = "plugin paste-dialog"
desc = "Paste with conflict dialog"
```
Restart yazi, then press <kbd>p</kbd> to paste with conflict resolution.
---
## Usage
1. Yank files (<kbd>y</kbd>) or cut them (<kbd>x</kbd>) as usual.
2. Navigate to the destination directory.
3. Press <kbd>p</kbd> to paste.
4. If there are conflicts a pick dialog appears for each one:
- Navigate with <kbd>j</kbd>/<kbd>k</kbd> or arrow keys, confirm with <kbd>Enter</kbd>.
- **Merge** — overwrites the destination file or merges directory contents.
- **Merge all remaining** — merge this and all remaining conflicts without prompting.
- **Replace** — deletes the destination entirely before copying (removes excess files).
- **Rename** — shows an input prompt with `name-yymmdd` pre-filled. If the new name also conflicts, the dialog reappears; choosing Rename again cycles the default to `name-2`, `name-3`, etc.
- **Skip** — leaves the destination untouched.
- **Cancel** (or <kbd>Esc</kbd>) — pastes any queued clean items, then aborts.
---
## Notes
- **Merge** uses the scheduler's existing `force=true` behavior: new files are
added, existing files are overwritten, destination-only files are preserved.
- **Replace** deletes the destination directory entirely before copying,
removing any files that exist only in the destination.
- Deep conflicts within merged directories are resolved silently by
overwriting individual files.
- **Cut + Skip** leaves the source file in place (it is not deleted).
- Between the conflict pre-scan and the actual paste, `overwrite=false` items
fall back to `unique_file()` if a new conflict appears.
## License
MIT

220
.config/yazi/plugins/paste-dialog.yazi/main.lua

@ -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

2
.gitignore vendored

@ -4,3 +4,5 @@
.config/nvim/.env
.local/share/applications/mimeinfo.cache
.docker/config.json
.crush

15
.zshrc

@ -106,6 +106,10 @@ fi
# Initialize modules.
source ${ZIM_HOME}/init.zsh
# Override bira prompt: user@host in kitty foreground gray
PS1='╭─%B%(!.%F{red]}.%F{#666666})%n@%m %F{blue}%~${(e)git_info[prompt]}${VIRTUAL_ENV:+" %F{green}‹${VIRTUAL_ENV:t}›"}%f%b
╰─%B%(!.#.$)%b '
# ------------------------------
# Post-init module configuration
# ------------------------------
@ -142,6 +146,13 @@ zmodload -F zsh/terminfo +p:terminfo
# }}} End configuration added by Zim install
#
# zsh-syntax-highlighting
#
# Use kitty foreground gray for commands instead of green
ZSH_HIGHLIGHT_STYLES[command]='fg=#666666'
# screen window naming
if [[ "$TERM" == screen* ]]; then
@ -164,10 +175,11 @@ if [[ "$TERM" == screen* ]]; then
fi
typeset -U path
local custom_paths=(
custom_paths=(
"$HOME/.cargo/bin"
"$HOME/.local/kitty.app/bin"
"$HOME/.local/bin"
"$HOME/go/bin"
)
for d in "${custom_paths[@]}"; do
@ -185,6 +197,7 @@ alias gl="git log --graph --abbrev-commit --decorate --format=format:'%C(bold bl
alias git-config-tb='git config user.name "Łukasz Chodyła" && git config user.email "lukasz.chodyla@transbit.com.pl"'
alias git-config-ch='git config user.name "chodak166" && git config user.email "chodak166@op.pl"'
alias config='/usr/bin/git --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME'
alias config-lazygit='lazygit --git-dir=$HOME/.dotfiles.git/ --work-tree=$HOME'
alias v='nvim'
alias se=sudoedit
alias trb-smb-mount='sudo mount -t cifs -o uid=`id -u chodylal` -o gid=`id -g chodylal` -o user=chodylal //trb-fs-p01/repozytorium/ /home/chodylal/smb'

Loading…
Cancel
Save