Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replace selected Text with String, via Keymap

I want to replace the selected Text (in Visual-Mode) with the current Date.

Currently I am trying to call a cmd and then use the 'change' method.

keymap.set("x", "<leader>nf", function()
    vim.cmd(string.format("insert\n%s", os.date("%d.%m.%Y")))
end)

All it does right now is to insert the Date in the line above the selection, not deleting it.

like image 224
Aninsi Sasberg Avatar asked Dec 04 '25 01:12

Aninsi Sasberg


2 Answers

The accepted answer uses '< and '>, but these marks have a drawback. They only update when the user (or script) leaves visual mode. An alternative is to use . and v as position arguments for getpos.

You can read more about these position arguments in the help for line(). Briefly, in visual mode, . and v complement each other: . refers to the position of the cursor (this use of . is common in vim and neovim), and v refers to the other end of the visual selection. You can think of v as where v_o would take you if you enter o while in visual mode.

. and v are especially useful in characterwise visual mode, since they are guaranteed to give you the start and end position of the visual selection. (If the cursor is at the end of the visual selection, . refers to the end and v refers to the start of the selection. If the cursor is at the start of the visual selection, . refers to the start and v refers to the end of the selection.)

In any case, adapting GreatGaga's code to use . and v gives you something like this. (I probably overcommented parts of it, but I didn't repeat the useful comments in GreatGaga's version. Check that for the (six!) arguments to nvim_buf_set_text):

local _should_swap = function(s_pos, e_pos)
    -- 1. If s_pos is on a line below e_pos, they are reversed.
    if s_pos[2] > e_pos[2] then
        return true
    end
    -- 2. If s_pos and e_pos are on the same line, and s_pos
    --    is in a later column, they are reversed.
    if s_pos[2] == e_pos[2] and s_pos[3] > e_pos[3] then
        return true
    end
    -- 3. If s_pos is on an earlier line than e_pos or the same line
    --    but an earlier column, they are not reversed.
    return false
end

local function replace_date()
    local s_pos = vim.fn.getpos("v")
    local e_pos = vim.fn.getpos(".")
    -- Depending on where the cursor is, s_pos may initially refer to
    -- the end of the visual selection, and e_pos may initially refer
    -- to the start. If so, we swap their values.
    if _should_swap(s_pos, e_pos) then
        s_pos, e_pos = e_pos, s_pos
    end

    local date = os.date("%d.%m.%Y")

    vim.api.nvim_buf_set_text(
        s_pos[1],
        s_pos[2] - 1,
        s_pos[3] - 1,
        e_pos[2] - 1,
        e_pos[3],
        { date }
    )
    vim.api.nvim_feedkeys(
        vim.api.nvim_replace_termcodes("<Esc>", true, false, true),
        "x",
        false
    )
end
like image 160
Telemachus Avatar answered Dec 06 '25 17:12

Telemachus


You can use a custom function you need to work with row and column of a selection of a buffer to make it happen. Other option is using the change block from visual selection. See h v_c

Replace visual selection with date (not v_c)

This solution fully removes all text from a selection and inserts a date

local function replace_date()
    -- Use unpack to give tuple values a name otherwise you can only use indexing.
    -- We are getting line, column, buffer nr etc based on the visual markers here.
    local s_buf, s_row, s_col, _ = unpack(vim.fn.getpos("'<"))
    local _, e_row, e_col, _ = unpack(vim.fn.getpos("'>"))
    local date = os.date("%d.%m.%Y")
    -- Indexing into buffer row needs - 1 because lua indexing starts from 1.
    -- Column subtracts are just to account for start and end scenarios.
    -- We use the positions to fully clear all selected text of a v-block.
    vim.api.nvim_buf_set_text(s_buf, e_row - 1, s_col - 1 , e_row - 1, e_col, {})
    -- Place date.
    vim.api.nvim_buf_set_text(s_buf, s_row - 1, s_col, s_row - 1, s_col, { date })
    -- Exit visual mode
    vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<esc>', true, false, true), 'x', false)
end
-- Bind to visual mode.
vim.keymap.set('v', '\\', replace_date, { noremap = true, silent = true })

When selection is done you can press \ in visual mode to change to date.

Insert into visual block keeping trailing text

This insert date into visual selection but keeps everything in v-block thats after the inserted date.

local function insert_date_v()
    local s_buf, s_row, s_col, _ = unpack(vim.fn.getpos("'<"))
    local _, e_row, e_col, _ = unpack(vim.fn.getpos("'>"))
    local date = os.date("%d.%m.%Y")
    -- Keeps trailing visual selection
    vim.api.nvim_buf_set_text(s_buf, e_row - 1, s_col - 1 , e_row - 1, e_col - 1, { date })
    vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<esc>', true, false, true), 'x', false)
end

vim.keymap.set('v', '\\', insert_date_v, { noremap = true, silent = true })

Use v_c

See :h v_c you can replace text by entering c after selecting a visual block. If you use c while in visual mode followed by\ the function below replaces it. Notice this is bound to insert mode and not visual. More keystrokes than the earlier solution but maybe less edgecases.

local function insert_date()
    local row, col = unpack(vim.api.nvim_win_get_cursor(0))
    local date = os.date("%d.%m.%Y")
    vim.api.nvim_buf_set_text(0, row - 1, col, row - 1, col, { date })
end

vim.keymap.set('i', '\\', insert_date, { noremap = true, silent = true })

Consider adding leader key to the bindings of above. use :h to check out the methods for tweaking.

Any of these should cover your needs.

like image 43
GreatGaja Avatar answered Dec 06 '25 16:12

GreatGaja