local utils = require("yanky.utils") local highlight = require("yanky.highlight") local system_clipboard = require("yanky.system_clipboard") local preserve_cursor = require("yanky.preserve_cursor") local picker = require("yanky.picker") local textobj = require("yanky.textobj") local yanky = {} yanky.ring = { state = nil, is_cycling = false, callback = nil, } yanky.direction = { FORWARD = 1, BACKWARD = -1, } yanky.type = { PUT_BEFORE = "P", PUT_AFTER = "p", GPUT_BEFORE = "gP", GPUT_AFTER = "gp", PUT_INDENT_AFTER = "]p", PUT_INDENT_BEFORE = "[p", } function yanky.setup(options) yanky.config = require("yanky.config") yanky.config.setup(options) yanky.history = require("yanky.history") yanky.history.setup() system_clipboard.setup() highlight.setup() preserve_cursor.setup() picker.setup() local yanky_augroup = vim.api.nvim_create_augroup("Yanky", { clear = true }) vim.api.nvim_create_autocmd("TextYankPost", { group = yanky_augroup, pattern = "*", callback = function(_) yanky.on_yank() end, }) if vim.v.vim_did_enter == 1 then yanky.init_history() else vim.api.nvim_create_autocmd("VimEnter", { group = yanky_augroup, pattern = "*", callback = yanky.init_history, }) end vim.api.nvim_create_user_command("YankyClearHistory", yanky.clear_history, {}) end function yanky.init_history() yanky.history.push(utils.get_register_info(utils.get_default_register())) yanky.history.sync_with_numbered_registers() end local function do_put(state, _) if state.is_visual then vim.cmd([[execute "normal! \"]]) end local ok, val = pcall( vim.cmd, string.format( 'silent normal! %s"%s%s%s', state.is_visual and "gv" or "", state.register ~= "=" and state.register or "=" .. vim.api.nvim_replace_termcodes("", true, false, true), state.count, state.type ) ) if not ok then vim.notify(val, vim.log.levels.WARN) return end highlight.highlight_put(state) end function yanky.put(type, is_visual, callback) if not vim.tbl_contains(vim.tbl_values(yanky.type), type) then vim.notify("Invalid type " .. type, vim.log.levels.ERROR) return end yanky.ring.state = nil yanky.ring.is_cycling = false yanky.ring.callback = callback or do_put -- On Yank event is not triggered when put from expression register, -- To allows cycling, we must store value here if vim.v.register == "=" then local entry = utils.get_register_info("=") entry.filetype = vim.bo.filetype yanky.history.push(entry) end yanky.init_ring(type, vim.v.register, vim.v.count, is_visual, yanky.ring.callback) end function yanky.clear_ring() if yanky.can_cycle() and nil ~= yanky.ring.state.augroup then vim.api.nvim_clear_autocmds({ group = yanky.ring.state.augroup }) end yanky.ring.state = nil yanky.ring.is_cycling = false end function yanky.attach_cancel() if yanky.config.options.ring.cancel_event == "move" then yanky.ring.state.augroup = vim.api.nvim_create_augroup("YankyRingClear", { clear = true }) vim.schedule(function() vim.api.nvim_create_autocmd("CursorMoved", { group = yanky.ring.state.augroup, buffer = 0, callback = yanky.clear_ring, }) end) else vim.api.nvim_buf_attach(0, false, { on_lines = function(_) yanky.clear_ring() return true end, }) end end function yanky.init_ring(type, register, count, is_visual, callback) register = (register ~= '"' and register ~= "_") and register or utils.get_default_register() local reg_content = vim.fn.getreg(register) if nil == reg_content or "" == reg_content then vim.notify(string.format('Register "%s" is empty', register), vim.log.levels.WARN) return end local new_state = { type = type, register = register, count = count > 0 and count or 1, is_visual = is_visual, use_repeat = callback == nil, } if nil ~= callback then callback(new_state, do_put) end yanky.ring.state = new_state yanky.ring.is_cycling = false yanky.attach_cancel() if yanky.config.options.textobj.enabled then textobj.save_put() end end function yanky.can_cycle() return nil ~= yanky.ring.state end function yanky.cycle(direction) if not yanky.can_cycle() then vim.notify("Your last action was not put, ignoring cycle", vim.log.levels.INFO) return end direction = direction or yanky.direction.FORWARD if not vim.tbl_contains(vim.tbl_values(yanky.direction), direction) then vim.notify("Invalid direction for cycle", vim.log.levels.ERROR) return end if nil ~= yanky.ring.state.augroup then vim.api.nvim_clear_autocmds({ group = yanky.ring.state.augroup }) end if not yanky.ring.is_cycling then yanky.history.reset() local reg = utils.get_register_info(yanky.ring.state.register) local first = yanky.history.first() if nil ~= first and reg.regcontents == first.regcontents and reg.regtype == first.regtype then yanky.history.skip() end end local new_state = yanky.ring.state local next_content if direction == yanky.direction.FORWARD then next_content = yanky.history.next() if nil == next_content then vim.notify("Reached oldest item", vim.log.levels.INFO) yanky.attach_cancel() return end else next_content = yanky.history.previous() if nil == next_content then vim.notify("Reached first item", vim.log.levels.INFO) yanky.attach_cancel() return end end yanky.ring.state.register = yanky.ring.state.register ~= "=" and yanky.ring.state.register or utils.get_default_register() utils.use_temporary_register(yanky.ring.state.register, next_content, function() if new_state.use_repeat then local ok, val = pcall(vim.cmd, "silent normal! u.") if not ok then vim.notify(val, vim.log.levels.WARN) yanky.attach_cancel() return end highlight.highlight_put(new_state) else local ok, val = pcall(vim.cmd, "silent normal! u") if not ok then vim.notify(val, vim.log.levels.WARN) yanky.attach_cancel() return end yanky.ring.callback(new_state, do_put) end end) if yanky.config.options.ring.update_register_on_cycle then vim.fn.setreg(new_state.register, next_content.regcontents, next_content.regtype) end yanky.ring.is_cycling = true yanky.ring.state = new_state yanky.attach_cancel() end function yanky.on_yank() if vim.tbl_contains(yanky.config.options.ring.ignore_registers, vim.v.register) then return end -- Only historize first delete in visual mode if vim.v.event.visual and vim.v.event.operator == "d" and yanky.ring.is_cycling then return end local entry = utils.get_register_info(vim.v.event.regname) entry.filetype = vim.bo.filetype yanky.history.push(entry) preserve_cursor.on_yank() end function yanky.yank(options) options = options or {} preserve_cursor.yank() return string.format("%sy", options.register and '"' .. options.register or "") end function yanky.clear_history() yanky.history.clear() end function yanky.register_plugs() local yanky_wrappers = require("yanky.wrappers") vim.keymap.set("n", "(YankyCycleForward)", function() yanky.cycle(1) end, { silent = true }) vim.keymap.set("n", "(YankyCycleBackward)", function() yanky.cycle(-1) end, { silent = true }) vim.keymap.set("n", "(YankyPreviousEntry)", function() yanky.cycle(1) end, { silent = true }) vim.keymap.set("n", "(YankyNextEntry)", function() yanky.cycle(-1) end, { silent = true }) vim.keymap.set({ "n", "x" }, "(YankyYank)", yanky.yank, { silent = true, expr = true }) for type, type_text in pairs({ p = "PutAfter", P = "PutBefore", gp = "GPutAfter", gP = "GPutBefore", ["]p"] = "PutIndentAfter", ["[p"] = "PutIndentBefore", }) do vim.keymap.set("n", string.format("(Yanky%s)", type_text), function() yanky.put(type, false) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%s)", type_text), function() yanky.put(type, true) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sJoined)", type_text), function() yanky.put(type, false, yanky_wrappers.trim_and_join_lines()) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sJoined)", type_text), function() yanky.put(type, true, yanky_wrappers.trim_and_join_lines()) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sLinewise)", type_text), function() yanky.put(type, false, yanky_wrappers.linewise()) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sLinewise)", type_text), function() yanky.put(type, true, yanky_wrappers.linewise()) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sLinewiseJoined)", type_text), function() yanky.put(type, false, yanky_wrappers.linewise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sLinewiseJoined)", type_text), function() yanky.put(type, true, yanky_wrappers.linewise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sCharwise)", type_text), function() yanky.put(type, false, yanky_wrappers.charwise()) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sCharwise)", type_text), function() yanky.put(type, true, yanky_wrappers.charwise()) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sCharwiseJoined)", type_text), function() yanky.put(type, false, yanky_wrappers.charwise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sCharwiseJoined)", type_text), function() yanky.put(type, true, yanky_wrappers.charwise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sBlockwise)", type_text), function() yanky.put(type, false, yanky_wrappers.blockwise()) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sBlockwise)", type_text), function() yanky.put(type, true, yanky_wrappers.blockwise()) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%sBlockwiseJoined)", type_text), function() yanky.put(type, false, yanky_wrappers.blockwise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%sBlockwiseJoined)", type_text), function() yanky.put(type, true, yanky_wrappers.blockwise(yanky_wrappers.trim_and_join_lines())) end, { silent = true }) for change, change_text in pairs({ [">>"] = "ShiftRight", ["<<"] = "ShiftLeft", ["=="] = "Filter" }) do vim.keymap.set("n", string.format("(Yanky%s%s)", type_text, change_text), function() yanky.put(type, false, yanky_wrappers.linewise(yanky_wrappers.change(change))) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%s%s)", type_text, change_text), function() yanky.put(type, true, yanky_wrappers.linewise(yanky_wrappers.change(change))) end, { silent = true }) vim.keymap.set("n", string.format("(Yanky%s%sJoined)", type_text, change_text), function() yanky.put( type, false, yanky_wrappers.linewise(yanky_wrappers.trim_and_join_lines(yanky_wrappers.change(change))) ) end, { silent = true }) vim.keymap.set("x", string.format("(Yanky%s%sJoined)", type_text, change_text), function() yanky.put( type, true, yanky_wrappers.linewise(yanky_wrappers.trim_and_join_lines(yanky_wrappers.change(change))) ) end, { silent = true }) end end end return yanky