Last active
February 28, 2025 02:08
-
-
Save DanSM-5/9283605c65af19b9a72086e7fc1027e2 to your computer and use it in GitHub Desktop.
Function to get the text from the visual selection in the vim editor. Vimscript version is compatible in both vim and neovim. Also lua only version.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- Function to get the selected text. | |
-- It handles when current mode is visual mode and when visual mode has ended. | |
-- It handle visual, visual line and visual block modes | |
-- The return is a list with the selected text per line from top to bottom and from left to right. | |
-- | |
-- Based on the answers in the stackoverflow post: | |
-- https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript/28398359 | |
-- | |
-- It has great answers but none quite behave as I expected, so this is my take on it. | |
-- If you want a lua version, check this up. | |
-- This is a best effor translation from the vimscript version. | |
-- If you find an issue or have a suggestion to improve it, feel free to | |
-- make the suggestion. | |
---Get the text between the marks a and b using the appropriate mode | |
---@param a_mark string Reference mark a | |
---@param b_mark string Reference mark b | |
---@param mode string Mode to process text from marks | |
---@return string[] Selected text. One entry per line from left to right. | |
local get_selected_text_marks = function (a_mark, b_mark, mode) | |
local _, line_start, column_start = unpack(vim.fn.getpos(a_mark)) | |
local _, line_end, column_end = unpack(vim.fn.getpos(b_mark)) | |
-- Mark could be reversed if starting selection from bottom to top or right to left | |
if (vim.fn.line2byte(line_start)+column_start) > (vim.fn.line2byte(line_end)+column_end) then | |
line_start, column_start, line_end, column_end = line_end, column_end, line_start, column_start | |
end | |
-- Should always be an array when passing two arguments | |
local lines = vim.fn.getline(line_start, line_end) --[[@as string[] ]] | |
-- No selection, return empty | |
if #lines == 0 then | |
return {} | |
end | |
-- Handle visual line selection | |
if mode == 'V' then | |
return lines -- No further process | |
end | |
-- Handle visual block selection | |
if mode == vim.keycode('<C-V>') then | |
-- Selection can be reversed if started from right to left | |
if column_start > column_end then | |
column_start, column_end = column_end, column_start | |
end | |
if vim.o.selection == 'exclusive' then | |
column_end = column_end - 1 -- Needed to remove the last character to make it match the visual selction | |
end | |
for idx = 1, #lines do | |
-- Get just the selected area from each line | |
lines[idx] = lines[idx]:sub(1, column_end) | |
lines[idx] = lines[idx]:sub(column_start) | |
end | |
return lines | |
end | |
-- Handle visual mode 'v' | |
if vim.o.selection == 'exclusive' then | |
column_end = column_end - 1 -- Needed to remove the last character to make it match the visual selction | |
end | |
-- Adjust first and last selected lines to the respective start/end position | |
lines[#lines] = lines[#lines]:sub(1, column_end) | |
lines[1] = lines[1]:sub(column_start) | |
return lines | |
end | |
---Get text from visual selected area | |
---@return string[] Selected text. One entry per line from left to right. | |
local get_selected_text = function () | |
local mode = vim.fn.mode() | |
if mode == 'v' or mode == 'V' or mode == vim.keycode('<C-V>') then | |
return get_selected_text_marks('v', '.', mode) | |
else | |
return get_selected_text_marks("'<", "'>", vim.fn.visualmode()) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
" Function to get the selected text. | |
" It handles when current mode is visual mode and when visual mode has ended. | |
" It handle visual, visual line and visual block modes | |
" The return is a list with the selected text per line from top to bottom and from left to right. | |
" | |
" Based on the answers in the stackoverflow post: | |
" https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript/28398359 | |
" | |
" It has great answers but none quite behave as I expected, so this is my take on it. | |
" Get selected text from mark positions | |
function! get_text_from_marks(a_mark, b_mark, mode) | |
let mode = a:mode | |
let [line_start, column_start] = getpos(a:a_mark)[1:2] | |
let [line_end, column_end] = getpos(a:b_mark)[1:2] | |
" Mark could be reversed if starting selection from bottom to top or right to left | |
if (line2byte(line_start)+column_start) > (line2byte(line_end)+column_end) | |
let [line_start, column_start, line_end, column_end] = | |
\ [line_end, column_end, line_start, column_start] | |
end | |
let lines = getline(line_start, line_end) | |
" No selection, return empty | |
if len(lines) == 0 | |
return [] | |
endif | |
" Handle visual line selection | |
if mode ==# 'V' | |
return lines " No further process | |
endif | |
" Handle visual block selection | |
if mode ==# "\<C-V>" | |
" Selection can be reversed if started from right to left | |
let [column_start, column_end] = column_end > column_start ? [column_start, column_end] : [column_end, column_start] | |
if &selection ==# "exclusive" | |
let column_end -= 1 " Needed to remove the last character to make it match the visual selction | |
endif | |
for idx in range(len(lines)) | |
" Get just the selected area from each line | |
let lines[idx] = lines[idx][: column_end - 1] | |
let lines[idx] = lines[idx][column_start - 1:] | |
endfor | |
return lines | |
endif | |
" Handle visual mode 'v' | |
if &selection ==# "exclusive" | |
let column_end -= 1 " Needed to remove the last character to make it match the visual selction | |
endif | |
" Adjust first and last selected lines to the respective start/end position | |
let lines[-1] = lines[-1][: column_end - 1] | |
let lines[ 0] = lines[ 0][column_start - 1:] | |
return lines | |
endfunction | |
function! get_selected_text() abort | |
" Check current mode | |
let curr_mode = mode() | |
if curr_mode ==# 'v' || curr_mode ==# 'V' || curr_mode ==# "\<C-V>" | |
" This is valid if currently in a type of visual mode trigger by | |
" something like `<cmd>call func(get_selected_text())<cr>` while in visual mode | |
" We can use positions 'v' (cursor) and '.' (oposite end) | |
return get_text_from_marks('v', '.', curr_mode) | |
else | |
" When current mode is not visual, it means the mode may have ended like from keymaps | |
" like `:<C-U>call func(get_selected_text())<cr>` which take you out of visual mode | |
" Then the last visual mode can be fetched with visualmode() and positions with the marks '< '> | |
return get_text_from_marks("'<", "'>", visualmode()) | |
end | |
endfunction |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment