mirror of
https://github.com/neovim/neovim.git
synced 2025-10-04 17:06:30 +00:00
vim-patch:9.1.0120: hard to get visual region using Vim script
Problem: hard to get visual region using Vim script
Solution: Add getregion() Vim script function
(Shougo Matsushita, Jakub Łuczyński)
closes: vim/vim#13998
closes: vim/vim#11579
3f905ab3c4
Cherry-pick changes from patch 9.1.0122, with :echom instead of :echow.
Co-authored-by: Shougo Matsushita <Shougo.Matsu@gmail.com>
Co-authored-by: Jakub Łuczyński <doubleloop@o2.pl>
This commit is contained in:
@@ -4355,6 +4355,49 @@ M.funcs = {
|
||||
returns = 'table',
|
||||
signature = 'getreginfo([{regname}])',
|
||||
},
|
||||
getregion = {
|
||||
args = 3,
|
||||
base = 1,
|
||||
desc = [=[
|
||||
Returns the list of strings from {pos1} to {pos2} as if it's
|
||||
selected in visual mode of {type}.
|
||||
For possible values of {pos1} and {pos2} see |line()|.
|
||||
{type} is the selection type:
|
||||
"v" for |charwise| mode
|
||||
"V" for |linewise| mode
|
||||
"<CTRL-V>" for |blockwise-visual| mode
|
||||
You can get the last selection type by |visualmode()|.
|
||||
If Visual mode is active, use |mode()| to get the Visual mode
|
||||
(e.g., in a |:vmap|).
|
||||
This function uses the line and column number from the
|
||||
specified position.
|
||||
It is useful to get text starting and ending in different
|
||||
columns, such as |charwise-visual| selection.
|
||||
|
||||
Note that:
|
||||
- Order of {pos1} and {pos2} doesn't matter, it will always
|
||||
return content from the upper left position to the lower
|
||||
right position.
|
||||
- If 'virtualedit' is enabled and selection is past the end of
|
||||
line, resulting lines are filled with blanks.
|
||||
- If the selection starts or ends in the middle of a multibyte
|
||||
character, it is not included but its selected part is
|
||||
substituted with spaces.
|
||||
- If {pos1} or {pos2} equals "v" (see |line()|) and it is not in
|
||||
|visual-mode|, an empty list is returned.
|
||||
- If {pos1}, {pos2} or {type} is an invalid string, an empty
|
||||
list is returned.
|
||||
|
||||
Examples: >
|
||||
:xnoremap <CR>
|
||||
\ <Cmd>echom getregion('v', '.', mode())<CR>
|
||||
<
|
||||
]=],
|
||||
name = 'getregion',
|
||||
params = { { 'pos1', 'string' }, { 'pos2', 'string' }, { 'type', 'string' } },
|
||||
returns = 'string[]',
|
||||
signature = 'getregion({pos1}, {pos2}, {type})',
|
||||
},
|
||||
getregtype = {
|
||||
args = { 0, 1 },
|
||||
base = 1,
|
||||
|
@@ -130,6 +130,7 @@
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/tag.h"
|
||||
#include "nvim/types_defs.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/version.h"
|
||||
#include "nvim/vim_defs.h"
|
||||
@@ -2801,6 +2802,149 @@ static void f_getpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
getpos_both(argvars, rettv, false, false);
|
||||
}
|
||||
|
||||
/// Convert from block_def to string
|
||||
static char *block_def2str(struct block_def *bd)
|
||||
{
|
||||
size_t size = (size_t)bd->startspaces + (size_t)bd->endspaces + (size_t)bd->textlen;
|
||||
char *ret = xmalloc(size + 1);
|
||||
char *p = ret;
|
||||
memset(p, ' ', (size_t)bd->startspaces);
|
||||
p += bd->startspaces;
|
||||
memmove(p, bd->textstart, (size_t)bd->textlen);
|
||||
p += bd->textlen;
|
||||
memset(p, ' ', (size_t)bd->endspaces);
|
||||
*(p + bd->endspaces) = NUL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// "getregion()" function
|
||||
static void f_getregion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
{
|
||||
tv_list_alloc_ret(rettv, kListLenMayKnow);
|
||||
|
||||
if (tv_check_for_string_arg(argvars, 0) == FAIL
|
||||
|| tv_check_for_string_arg(argvars, 1) == FAIL
|
||||
|| tv_check_for_string_arg(argvars, 2) == FAIL) {
|
||||
return;
|
||||
}
|
||||
|
||||
int fnum = -1;
|
||||
// NOTE: var2fpos() returns static pointer.
|
||||
pos_T *fp = var2fpos(&argvars[0], true, &fnum, false);
|
||||
if (fp == NULL) {
|
||||
return;
|
||||
}
|
||||
pos_T p1 = *fp;
|
||||
|
||||
fp = var2fpos(&argvars[1], true, &fnum, false);
|
||||
if (fp == NULL) {
|
||||
return;
|
||||
}
|
||||
pos_T p2 = *fp;
|
||||
|
||||
const char *pos1 = tv_get_string(&argvars[0]);
|
||||
const char *pos2 = tv_get_string(&argvars[1]);
|
||||
const char *type = tv_get_string(&argvars[2]);
|
||||
|
||||
const bool is_visual
|
||||
= (pos1[0] == 'v' && pos1[1] == NUL) || (pos2[0] == 'v' && pos2[1] == NUL);
|
||||
|
||||
if (is_visual && !VIsual_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
MotionType region_type = kMTUnknown;
|
||||
if (type[0] == 'v' && type[1] == NUL) {
|
||||
region_type = kMTCharWise;
|
||||
} else if (type[0] == 'V' && type[1] == NUL) {
|
||||
region_type = kMTLineWise;
|
||||
} else if (type[0] == Ctrl_V && type[1] == NUL) {
|
||||
region_type = kMTBlockWise;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const TriState save_virtual = virtual_op;
|
||||
virtual_op = virtual_active();
|
||||
|
||||
if (!lt(p1, p2)) {
|
||||
// swap position
|
||||
pos_T p = p1;
|
||||
p1 = p2;
|
||||
p2 = p;
|
||||
}
|
||||
|
||||
oparg_T oap;
|
||||
bool inclusive = true;
|
||||
|
||||
if (region_type == kMTCharWise) {
|
||||
// handle 'selection' == "exclusive"
|
||||
if (*p_sel == 'e' && !equalpos(p1, p2)) {
|
||||
if (p2.coladd > 0) {
|
||||
p2.coladd--;
|
||||
} else if (p2.col > 0) {
|
||||
p2.col--;
|
||||
mark_mb_adjustpos(curbuf, &p2);
|
||||
} else if (p2.lnum > 1) {
|
||||
p2.lnum--;
|
||||
p2.col = (colnr_T)strlen(ml_get(p2.lnum));
|
||||
if (p2.col > 0) {
|
||||
p2.col--;
|
||||
mark_mb_adjustpos(curbuf, &p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if fp2 is on NUL (empty line) inclusive becomes false
|
||||
if (*ml_get_pos(&p2) == NUL && !virtual_op) {
|
||||
inclusive = false;
|
||||
}
|
||||
} else if (region_type == kMTBlockWise) {
|
||||
colnr_T sc1, ec1, sc2, ec2;
|
||||
getvvcol(curwin, &p1, &sc1, NULL, &ec1);
|
||||
getvvcol(curwin, &p2, &sc2, NULL, &ec2);
|
||||
oap.motion_type = kMTBlockWise;
|
||||
oap.inclusive = true;
|
||||
oap.op_type = OP_NOP;
|
||||
oap.start = p1;
|
||||
oap.end = p2;
|
||||
oap.start_vcol = MIN(sc1, sc2);
|
||||
if (*p_sel == 'e' && ec1 < sc2 && 0 < sc2 && ec2 > ec1) {
|
||||
oap.end_vcol = sc2 - 1;
|
||||
} else {
|
||||
oap.end_vcol = MAX(ec1, ec2);
|
||||
}
|
||||
}
|
||||
|
||||
// Include the trailing byte of a multi-byte char.
|
||||
int l = utfc_ptr2len(ml_get_pos(&p2));
|
||||
if (l > 1) {
|
||||
p2.col += l - 1;
|
||||
}
|
||||
|
||||
for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) {
|
||||
char *akt = NULL;
|
||||
|
||||
if (region_type == kMTLineWise) {
|
||||
akt = xstrdup(ml_get(lnum));
|
||||
} else if (region_type == kMTBlockWise) {
|
||||
struct block_def bd;
|
||||
block_prep(&oap, &bd, lnum, false);
|
||||
akt = block_def2str(&bd);
|
||||
} else if (p1.lnum < lnum && lnum < p2.lnum) {
|
||||
akt = xstrdup(ml_get(lnum));
|
||||
} else {
|
||||
struct block_def bd;
|
||||
charwise_block_prep(p1, p2, &bd, lnum, inclusive);
|
||||
akt = block_def2str(&bd);
|
||||
}
|
||||
|
||||
assert(akt != NULL);
|
||||
tv_list_append_allocated_string(rettv->vval.v_list, akt);
|
||||
}
|
||||
|
||||
virtual_op = save_virtual;
|
||||
}
|
||||
|
||||
/// Common between getreg(), getreginfo() and getregtype(): get the register
|
||||
/// name from the first argument.
|
||||
/// Returns zero on error.
|
||||
|
141
src/nvim/ops.c
141
src/nvim/ops.c
@@ -84,25 +84,6 @@ static bool clipboard_delay_update = false; // delay clipboard update
|
||||
static bool clipboard_needs_update = false; // clipboard was updated
|
||||
static bool clipboard_didwarn = false;
|
||||
|
||||
// structure used by block_prep, op_delete and op_yank for blockwise operators
|
||||
// also op_change, op_shift, op_insert, op_replace - AKelly
|
||||
struct block_def {
|
||||
int startspaces; // 'extra' cols before first char
|
||||
int endspaces; // 'extra' cols after last char
|
||||
int textlen; // chars in block
|
||||
char *textstart; // pointer to 1st char (partially) in block
|
||||
colnr_T textcol; // index of chars (partially) in block
|
||||
colnr_T start_vcol; // start col of 1st char wholly inside block
|
||||
colnr_T end_vcol; // start col of 1st char wholly after block
|
||||
int is_short; // true if line is too short to fit in block
|
||||
int is_MAX; // true if curswant==MAXCOL when starting
|
||||
int is_oneChar; // true if block within one character
|
||||
int pre_whitesp; // screen cols of ws before block
|
||||
int pre_whitesp_c; // chars of ws before block
|
||||
colnr_T end_char_vcols; // number of vcols of post-block char
|
||||
colnr_T start_char_vcols; // number of vcols of pre-block char
|
||||
};
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "ops.c.generated.h"
|
||||
#endif
|
||||
@@ -2655,66 +2636,11 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
|
||||
reg->y_array[y_idx] = xstrdup(ml_get(lnum));
|
||||
break;
|
||||
|
||||
case kMTCharWise: {
|
||||
colnr_T startcol = 0;
|
||||
colnr_T endcol = MAXCOL;
|
||||
bool is_oneChar = false;
|
||||
colnr_T cs, ce;
|
||||
char *p = ml_get(lnum);
|
||||
bd.startspaces = 0;
|
||||
bd.endspaces = 0;
|
||||
|
||||
if (lnum == oap->start.lnum) {
|
||||
startcol = oap->start.col;
|
||||
if (virtual_op) {
|
||||
getvcol(curwin, &oap->start, &cs, NULL, &ce);
|
||||
if (ce != cs && oap->start.coladd > 0) {
|
||||
// Part of a tab selected -- but don't double-count it.
|
||||
bd.startspaces = (ce - cs + 1) - oap->start.coladd;
|
||||
if (bd.startspaces < 0) {
|
||||
bd.startspaces = 0;
|
||||
}
|
||||
startcol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lnum == oap->end.lnum) {
|
||||
endcol = oap->end.col;
|
||||
if (virtual_op) {
|
||||
getvcol(curwin, &oap->end, &cs, NULL, &ce);
|
||||
if (p[endcol] == NUL || (cs + oap->end.coladd < ce
|
||||
// Don't add space for double-wide
|
||||
// char; endcol will be on last byte
|
||||
// of multi-byte char.
|
||||
&& utf_head_off(p, p + endcol) == 0)) {
|
||||
if (oap->start.lnum == oap->end.lnum
|
||||
&& oap->start.col == oap->end.col) {
|
||||
// Special case: inside a single char
|
||||
is_oneChar = true;
|
||||
bd.startspaces = oap->end.coladd
|
||||
- oap->start.coladd + oap->inclusive;
|
||||
endcol = startcol;
|
||||
} else {
|
||||
bd.endspaces = oap->end.coladd
|
||||
+ oap->inclusive;
|
||||
endcol -= oap->inclusive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (endcol == MAXCOL) {
|
||||
endcol = (colnr_T)strlen(p);
|
||||
}
|
||||
if (startcol > endcol || is_oneChar) {
|
||||
bd.textlen = 0;
|
||||
} else {
|
||||
bd.textlen = endcol - startcol + oap->inclusive;
|
||||
}
|
||||
bd.textstart = p + startcol;
|
||||
case kMTCharWise:
|
||||
charwise_block_prep(oap->start, oap->end, &bd, lnum, oap->inclusive);
|
||||
yank_copy_line(reg, &bd, y_idx, false);
|
||||
break;
|
||||
}
|
||||
|
||||
// NOTREACHED
|
||||
case kMTUnknown:
|
||||
abort();
|
||||
@@ -4203,7 +4129,7 @@ static void restore_lbr(bool lbr_saved)
|
||||
/// - textlen includes the first/last char to be wholly yanked
|
||||
/// - start/endspaces is the number of columns of the first/last yanked char
|
||||
/// that are to be yanked.
|
||||
static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
|
||||
void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
|
||||
{
|
||||
int incr = 0;
|
||||
// Avoid a problem with unwanted linebreaks in block mode.
|
||||
@@ -4326,6 +4252,65 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
|
||||
restore_lbr(lbr_saved);
|
||||
}
|
||||
|
||||
/// Get block text from "start" to "end"
|
||||
void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T lnum,
|
||||
bool inclusive)
|
||||
{
|
||||
colnr_T startcol = 0;
|
||||
colnr_T endcol = MAXCOL;
|
||||
bool is_oneChar = false;
|
||||
colnr_T cs, ce;
|
||||
char *p = ml_get(lnum);
|
||||
bdp->startspaces = 0;
|
||||
bdp->endspaces = 0;
|
||||
|
||||
if (lnum == start.lnum) {
|
||||
startcol = start.col;
|
||||
if (virtual_op) {
|
||||
getvcol(curwin, &start, &cs, NULL, &ce);
|
||||
if (ce != cs && start.coladd > 0) {
|
||||
// Part of a tab selected -- but don't double-count it.
|
||||
bdp->startspaces = (ce - cs + 1) - start.coladd;
|
||||
if (bdp->startspaces < 0) {
|
||||
bdp->startspaces = 0;
|
||||
}
|
||||
startcol++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lnum == end.lnum) {
|
||||
endcol = end.col;
|
||||
if (virtual_op) {
|
||||
getvcol(curwin, &end, &cs, NULL, &ce);
|
||||
if (p[endcol] == NUL || (cs + end.coladd < ce
|
||||
// Don't add space for double-wide
|
||||
// char; endcol will be on last byte
|
||||
// of multi-byte char.
|
||||
&& utf_head_off(p, p + endcol) == 0)) {
|
||||
if (start.lnum == end.lnum && start.col == end.col) {
|
||||
// Special case: inside a single char
|
||||
is_oneChar = true;
|
||||
bdp->startspaces = end.coladd - start.coladd + inclusive;
|
||||
endcol = startcol;
|
||||
} else {
|
||||
bdp->endspaces = end.coladd + inclusive;
|
||||
endcol -= inclusive;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (endcol == MAXCOL) {
|
||||
endcol = (colnr_T)strlen(p);
|
||||
}
|
||||
if (startcol > endcol || is_oneChar) {
|
||||
bdp->textlen = 0;
|
||||
} else {
|
||||
bdp->textlen = endcol - startcol + inclusive;
|
||||
}
|
||||
bdp->textstart = p + startcol;
|
||||
}
|
||||
|
||||
/// Handle the add/subtract operator.
|
||||
///
|
||||
/// @param[in] oap Arguments of operator.
|
||||
|
@@ -15,6 +15,25 @@
|
||||
#include "nvim/pos_defs.h"
|
||||
#include "nvim/types_defs.h"
|
||||
|
||||
/// structure used by block_prep, op_delete and op_yank for blockwise operators
|
||||
/// also op_change, op_shift, op_insert, op_replace - AKelly
|
||||
struct block_def {
|
||||
int startspaces; ///< 'extra' cols before first char
|
||||
int endspaces; ///< 'extra' cols after last char
|
||||
int textlen; ///< chars in block
|
||||
char *textstart; ///< pointer to 1st char (partially) in block
|
||||
colnr_T textcol; ///< index of chars (partially) in block
|
||||
colnr_T start_vcol; ///< start col of 1st char wholly inside block
|
||||
colnr_T end_vcol; ///< start col of 1st char wholly after block
|
||||
int is_short; ///< true if line is too short to fit in block
|
||||
int is_MAX; ///< true if curswant==MAXCOL when starting
|
||||
int is_oneChar; ///< true if block within one character
|
||||
int pre_whitesp; ///< screen cols of ws before block
|
||||
int pre_whitesp_c; ///< chars of ws before block
|
||||
colnr_T end_char_vcols; ///< number of vcols of post-block char
|
||||
colnr_T start_char_vcols; ///< number of vcols of pre-block char
|
||||
};
|
||||
|
||||
typedef int (*Indenter)(void);
|
||||
|
||||
/// flags for do_put()
|
||||
|
Reference in New Issue
Block a user