aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/commitlint.yml4
-rw-r--r--runtime/autoload/dist/ft.vim5
-rw-r--r--runtime/doc/builtin.txt128
-rw-r--r--runtime/doc/eval.txt18
-rw-r--r--runtime/doc/usr_41.txt5
-rw-r--r--src/nvim/CMakeLists.txt3
-rw-r--r--src/nvim/eval.c145
-rw-r--r--src/nvim/eval.lua7
-rw-r--r--src/nvim/eval/funcs.c326
-rw-r--r--src/nvim/eval/typval.c2
-rw-r--r--src/nvim/ex_cmds.c10
-rw-r--r--src/nvim/regexp.c15
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/testdir/test_cursor_func.vim190
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim8
-rw-r--r--src/nvim/testdir/test_filetype.vim616
-rw-r--r--src/nvim/testdir/test_functions.vim27
-rw-r--r--src/nvim/testdir/test_vimscript.vim20
-rw-r--r--test/functional/ui/inccommand_spec.lua64
19 files changed, 1117 insertions, 478 deletions
diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml
index 5044b7dcab..f190981322 100644
--- a/.github/workflows/commitlint.yml
+++ b/.github/workflows/commitlint.yml
@@ -13,7 +13,9 @@ jobs:
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
+ path: pr_nvim
- uses: rhysd/action-setup-vim@v1
with:
neovim: true
- - run: nvim --clean -es +"lua require('scripts.lintcommit').main({trace=true})"
+ - run: wget https://raw.githubusercontent.com/neovim/neovim/master/scripts/lintcommit.lua
+ - run: nvim --clean -es +"cd pr_nvim" +"lua dofile('../lintcommit.lua').main({trace=true})"
diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim
index 5d8734a625..aacecc521e 100644
--- a/runtime/autoload/dist/ft.vim
+++ b/runtime/autoload/dist/ft.vim
@@ -726,7 +726,7 @@ func dist#ft#FTperl()
endif
let save_cursor = getpos('.')
call cursor(1,1)
- let has_use = search('^use\s\s*\k', 'c', 30)
+ let has_use = search('^use\s\s*\k', 'c', 30) > 0
call setpos('.', save_cursor)
if has_use
setf perl
@@ -758,7 +758,8 @@ func dist#ft#FTtex()
let save_cursor = getpos('.')
call cursor(1,1)
let firstNC = search('^\s*[^[:space:]%]', 'c', 1000)
- if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword.
+ if firstNC > 0
+ " Check the next thousand lines for a LaTeX or ConTeXt keyword.
let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>'
let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>'
let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)',
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 327b8dc4d9..c68e86462d 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -75,12 +75,13 @@ changenr() Number current change number
chanclose({id}[, {stream}]) Number Closes a channel or one of its streams
chansend({id}, {data}) Number Writes {data} to channel
char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
+charcol({expr}) Number column number of cursor or mark
charidx({string}, {idx} [, {countcc}])
Number char index of byte {idx} in {string}
chdir({dir}) String change current working directory
cindent({lnum}) Number C indent for line {lnum}
clearmatches([{win}]) none clear all matches
-col({expr}) Number column nr of cursor or mark
+col({expr}) Number column byte index of cursor or mark
complete({startcol}, {matches}) none set Insert mode completion
complete_add({expr}) Number add completion match
complete_check() Number check for key typed during completion
@@ -170,6 +171,7 @@ getchangelist([{buf}]) List list of change list items
getchar([expr]) Number or String
get one character from the user
getcharmod() Number modifiers for the last typed character
+getcharpos({expr}) List position of cursor, mark, etc.
getcharsearch() Dict last character search
getcharstr([expr]) String get one character from the user
getcmdline() String return the current command-line
@@ -178,7 +180,8 @@ getcmdtype() String return current command-line type
getcmdwintype() String return current command-line window type
getcompletion({pat}, {type} [, {filtered}])
List list of cmdline completion matches
-getcurpos() List position of the cursor
+getcurpos([{winnr}]) List position of the cursor
+getcursorcharpos([{winnr}]) List character position of the cursor
getcwd([{winnr} [, {tabnr}]]) String get the current working directory
getenv({name}) String return environment variable
getfontname([{name}]) String name of font being used
@@ -387,8 +390,10 @@ setbufline( {expr}, {lnum}, {line})
Number set line {lnum} to {line} in buffer
{expr}
setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val}
+setcharpos({expr}, {list}) Number set the {expr} position to {list}
setcharsearch({dict}) Dict set character search from {dict}
setcmdpos({pos}) Number set cursor position in command-line
+setcursorcharpos({list}) Number move cursor to position in {list}
setenv({name}, {val}) none set environment variable
setfperm({fname}, {mode} Number set {fname} file permissions to {mode}
setline({lnum}, {line}) Number set line {lnum} to {line}
@@ -949,8 +954,8 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()*
< The first and third echo result in 3 ('e' plus composing
character is 3 bytes), the second echo results in 1 ('e' is
one byte).
- Only works differently from byteidx() when 'encoding' is set to
- a Unicode encoding.
+ Only works differently from byteidx() when 'encoding' is set
+ to a Unicode encoding.
Can also be used as a |method|: >
GetName()->byteidxcomp(idx)
@@ -1035,6 +1040,18 @@ char2nr({string} [, {utf8}]) *char2nr()*
Can also be used as a |method|: >
GetChar()->char2nr()
<
+ *charcol()*
+charcol({expr}) Same as |col()| but returns the character index of the column
+ position given with {expr} instead of the byte position.
+
+ Example:
+ With the cursor on '세' in line 5 with text "여보세요": >
+ charcol('.') returns 3
+ col('.') returns 7
+
+< Can also be used as a |method|: >
+ GetPos()->col()
+<
*charidx()*
charidx({string}, {idx} [, {countcc}])
Return the character index of the byte at {idx} in {string}.
@@ -1120,7 +1137,8 @@ col({expr}) The result is a Number, which is the byte index of the column
out of range then col() returns zero.
To get the line number use |line()|. To get both use
|getpos()|.
- For the screen column position use |virtcol()|.
+ For the screen column position use |virtcol()|. For the
+ character position use |charcol()|.
Note that only marks in the current file can be used.
Examples: >
col(".") column of cursor
@@ -1443,6 +1461,9 @@ cursor({list})
This is like the return value of |getpos()| or |getcurpos()|,
but without the first item.
+ To position the cursor using the character count, use
+ |setcursorcharpos()|.
+
Does not change the jumplist.
If {lnum} is greater than the number of lines in the buffer,
the cursor will be positioned at the last line in the buffer.
@@ -2652,6 +2673,20 @@ getcharmod() *getcharmod()*
character itself are obtained. Thus Shift-a results in "A"
without a modifier.
+ *getcharpos()*
+getcharpos({expr})
+ Get the position for String {expr}. Same as |getpos()| but the
+ column number in the returned List is a character index
+ instead of a byte index.
+
+ Example:
+ With the cursor on '세' in line 5 with text "여보세요": >
+ getcharpos('.') returns [0, 5, 3, 0]
+ getpos('.') returns [0, 5, 7, 0]
+<
+ Can also be used as a |method|: >
+ GetMark()->getcharpos()
+
getcharsearch() *getcharsearch()*
Return the current character search information as a {dict}
with the following entries:
@@ -2786,12 +2821,22 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
GetPattern()->getcompletion('color')
<
*getcurpos()*
-getcurpos() Get the position of the cursor. This is like getpos('.'), but
+getcurpos([{winid}])
+ Get the position of the cursor. This is like getpos('.'), but
includes an extra "curswant" in the list:
[0, lnum, col, off, curswant] ~
The "curswant" number is the preferred column when moving the
- cursor vertically. Also see |getpos()|.
- The first "bufnum" item is always zero.
+ cursor vertically. Also see |getcursorcharpos()| and
+ |getpos()|.
+ The first "bufnum" item is always zero. The byte position of
+ the cursor is returned in 'col'. To get the character
+ position, use |getcursorcharpos()|.
+
+ The optional {winid} argument can specify the window. It can
+ be the window number or the |window-ID|. The last known
+ cursor position is returned, this may be invalid for the
+ current value of the buffer if it is not the current window.
+ If {winid} is invalid a list with zeroes is returned.
This can be used to save and restore the cursor position: >
let save_cursor = getcurpos()
@@ -2800,6 +2845,22 @@ getcurpos() Get the position of the cursor. This is like getpos('.'), but
< Note that this only works within the window. See
|winrestview()| for restoring more state.
+ Can also be used as a |method|: >
+ GetWinid()->getcurpos()
+<
+ *getcursorcharpos()*
+getcursorcharpos([{winid}])
+ Same as |getcurpos()| but the column number in the returned
+ List is a character index instead of a byte index.
+
+ Example:
+ With the cursor on '보' in line 3 with text "여보세요": >
+ getcursorcharpos() returns [0, 3, 2, 0, 3]
+ getcurpos() returns [0, 3, 4, 0, 3]
+<
+ Can also be used as a |method|: >
+ GetWinid()->getcursorcharpos()
+
getcwd([{winnr}[, {tabnr}]]) *getcwd()*
With no arguments, returns the name of the effective
|current-directory|. With {winnr} or {tabnr} the working
@@ -3079,14 +3140,15 @@ getpos({expr}) Get the position for String {expr}. For possible values of
(visual line mode) the column of '< is zero and the column of
'> is a large number.
The column number in the returned List is the byte position
- within the line.
+ within the line. To get the character position in the line,
+ use |getcharpos()|.
The column number can be very large, e.g. 2147483647, in which
case it means "after the end of the line".
This can be used to save and restore the position of a mark: >
let save_a_mark = getpos("'a")
...
call setpos("'a", save_a_mark)
-< Also see |getcurpos()| and |setpos()|.
+< Also see |getcharpos()|, |getcurpos()| and |setpos()|.
Can also be used as a |method|: >
GetMark()->getpos()
@@ -4848,8 +4910,10 @@ matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()*
GetText()->matchstrpos('word')
<
*max()*
-max({expr}) Return the maximum value of all items in {expr}.
- {expr} can be a |List| or a |Dictionary|. For a Dictionary,
+max({expr}) Return the maximum value of all items in {expr}. Example: >
+ echo max([apples, pears, oranges])
+
+< {expr} can be a |List| or a |Dictionary|. For a Dictionary,
it returns the maximum of all values in the Dictionary.
If {expr} is neither a List nor a Dictionary, or one of the
items in {expr} cannot be used as a Number this results in
@@ -4905,8 +4969,10 @@ menu_get({path} [, {modes}]) *menu_get()*
<
*min()*
-min({expr}) Return the minimum value of all items in {expr}.
- {expr} can be a |List| or a |Dictionary|. For a Dictionary,
+min({expr}) Return the minimum value of all items in {expr}. Example: >
+ echo min([apples, pears, oranges])
+
+< {expr} can be a |List| or a |Dictionary|. For a Dictionary,
it returns the minimum of all values in the Dictionary.
If {expr} is neither a List nor a Dictionary, or one of the
items in {expr} cannot be used as a Number this results in
@@ -6447,6 +6513,20 @@ setbufvar({buf}, {varname}, {val}) *setbufvar()*
third argument: >
GetValue()->setbufvar(buf, varname)
+setcharpos({expr}, {list}) *setcharpos()*
+ Same as |setpos()| but uses the specified column number as the
+ character index instead of the byte index in the line.
+
+ Example:
+ With the text "여보세요" in line 8: >
+ call setcharpos('.', [0, 8, 4, 0])
+< positions the cursor on the fourth character '요'. >
+ call setpos('.', [0, 8, 4, 0])
+< positions the cursor on the second character '보'.
+
+ Can also be used as a |method|: >
+ GetPosition()->setcharpos('.')
+
setcharsearch({dict}) *setcharsearch()*
Set the current character search information to {dict},
which contains one or more of the following entries:
@@ -6488,6 +6568,21 @@ setcmdpos({pos}) *setcmdpos()*
Can also be used as a |method|: >
GetPos()->setcmdpos()
+setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()*
+setcursorcharpos({list})
+ Same as |cursor()| but uses the specified column number as the
+ character index instead of the byte index in the line.
+
+ Example:
+ With the text "여보세요" in line 4: >
+ call setcursorcharpos(4, 3)
+< positions the cursor on the third character '세'. >
+ call cursor(4, 3)
+< positions the cursor on the first character '여'.
+
+ Can also be used as a |method|: >
+ GetCursorPos()->setcursorcharpos()
+
setenv({name}, {val}) *setenv()*
Set environment variable {name} to {val}. Example: >
call setenv('HOME', '/home/myhome')
@@ -6600,7 +6695,8 @@ setpos({expr}, {list})
"lnum" and "col" are the position in the buffer. The first
column is 1. Use a zero "lnum" to delete a mark. If "col" is
- smaller than 1 then 1 is used.
+ smaller than 1 then 1 is used. To use the character count
+ instead of the byte count, use |setcharpos()|.
The "off" number is only used when 'virtualedit' is set. Then
it is the offset in screen columns from the start of the
@@ -6620,7 +6716,7 @@ setpos({expr}, {list})
Returns 0 when the position could be set, -1 otherwise.
An error message is given if {expr} is invalid.
- Also see |getpos()| and |getcurpos()|.
+ Also see |setcharpos()|, |getpos()| and |getcurpos()|.
This does not restore the preferred column for moving
vertically; if you set the cursor position with this, |j| and
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index d6486073cf..fc788fba59 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -372,8 +372,8 @@ Changing the order of items in a list: >
For loop ~
-The |:for| loop executes commands for each item in a |List| or |Blob|.
-A variable is set to each item in the sequence. Example with a List: >
+The |:for| loop executes commands for each item in a |List|, |String| or |Blob|.
+A variable is set to each item in sequence. Example with a List: >
:for item in mylist
: call Doit(item)
:endfor
@@ -390,7 +390,7 @@ If all you want to do is modify each item in the list then the |map()|
function will be a simpler method than a for loop.
Just like the |:let| command, |:for| also accepts a list of variables. This
-requires the argument to be a list of lists. >
+requires the argument to be a List of Lists. >
:for [lnum, col] in [[1, 3], [2, 8], [3, 0]]
: call Doit(lnum, col)
:endfor
@@ -408,6 +408,12 @@ It is also possible to put remaining items in a List variable: >
For a Blob one byte at a time is used.
+For a String one character, including any composing characters, is used as a
+String. Example: >
+ for c in text
+ echo 'This character is ' .. c
+ endfor
+
List functions ~
*E714*
@@ -1066,7 +1072,7 @@ expr7 *expr7*
For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one).
For '-' the sign of the number is changed.
-For '+' the number is unchanged.
+For '+' the number is unchanged. Note: "++" has no effect.
A String will be converted to a Number first.
@@ -1228,8 +1234,8 @@ And NOT: >
number
------
number number constant *expr-number*
- *hex-number* *octal-number* *binary-number*
+ *0x* *hex-number* *0o* *octal-number* *binary-number*
Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B)
and Octal (starting with 0, 0o or 0O).
@@ -1486,7 +1492,7 @@ Notice how execute() is used to execute an Ex command. That's ugly though.
Lambda expressions have internal names like '<lambda>42'. If you get an error
for a lambda expression, you can find what it is with the following command: >
- :function {'<lambda>42'}
+ :function <lambda>42
See also: |numbered-function|
==============================================================================
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 5dde170a12..bf29c94d51 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -746,6 +746,11 @@ Cursor and mark position: *cursor-functions* *mark-functions*
screenchar() get character code at a screen line/row
screenchars() get character codes at a screen line/row
screenstring() get string of characters at a screen line/row
+ charcol() character number of the cursor or a mark
+ getcharpos() get character position of cursor, mark, etc.
+ setcharpos() set character position of cursor, mark, etc.
+ getcursorcharpos() get character position of the cursor
+ setcursorcharpos() set character position of the cursor
Working with text in the current buffer: *text-functions*
getline() get a line or list of lines from the buffer
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 94572b57cd..dcc20194f0 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -493,6 +493,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
+if(MSVC)
+ install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
+endif()
set_property(TARGET nvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 5d9326eb24..dccad5a2d0 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -69,6 +69,7 @@ static char *e_nowhitespace
static char *e_invalwindow = N_("E957: Invalid window number");
static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
static char *e_write2 = N_("E80: Error while writing: %s");
+static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required");
// TODO(ZyX-I): move to eval/executor
static char *e_letwrong = N_("E734: Wrong variable type for %s=");
@@ -112,9 +113,11 @@ typedef struct {
int fi_semicolon; // TRUE if ending in '; var]'
int fi_varcount; // nr of variables in the list
listwatch_T fi_lw; // keep an eye on the item used.
- list_T *fi_list; // list being used
+ list_T *fi_list; // list being used
int fi_bi; // index of blob
blob_T *fi_blob; // blob being used
+ char_u *fi_string; // copy of string being used
+ int fi_byte_idx; // byte index in fi_string
} forinfo_T;
// values for vv_flags:
@@ -2641,8 +2644,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip)
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
+ } else if (tv.v_type == VAR_STRING) {
+ fi->fi_byte_idx = 0;
+ fi->fi_string = tv.vval.v_string;
+ tv.vval.v_string = NULL;
+ if (fi->fi_string == NULL) {
+ fi->fi_string = vim_strsave((char_u *)"");
+ }
} else {
- emsg(_(e_listblobreq));
+ emsg(_(e_string_list_or_blob_required));
tv_clear(&tv);
}
}
@@ -2679,6 +2689,22 @@ bool next_for_item(void *fi_void, char_u *arg)
fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
}
+ if (fi->fi_string != NULL) {
+ const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx);
+ if (len == 0) {
+ return false;
+ }
+ typval_T tv;
+ tv.v_type = VAR_STRING;
+ tv.v_lock = VAR_FIXED;
+ tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len);
+ fi->fi_byte_idx += len;
+ const int result
+ = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
+ xfree(tv.vval.v_string);
+ return result;
+ }
+
listitem_T *item = fi->fi_lw.lw_item;
if (item == NULL) {
return false;
@@ -2698,12 +2724,16 @@ void free_for_info(void *fi_void)
{
forinfo_T *fi = (forinfo_T *)fi_void;
- if (fi != NULL && fi->fi_list != NULL) {
+ if (fi == NULL) {
+ return;
+ }
+ if (fi->fi_list != NULL) {
tv_list_watch_remove(fi->fi_list, &fi->fi_lw);
tv_list_unref(fi->fi_list);
- }
- if (fi != NULL && fi->fi_blob != NULL) {
+ } else if (fi->fi_blob != NULL) {
tv_blob_unref(fi->fi_blob);
+ } else {
+ xfree(fi->fi_string);
}
xfree(fi);
}
@@ -8151,6 +8181,52 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
return ret;
}
+/// Convert the specified byte index of line 'lnum' in buffer 'buf' to a
+/// character index. Works only for loaded buffers. Returns -1 on failure.
+/// The index of the first character is one.
+int buf_byteidx_to_charidx(buf_T *buf, int lnum, int byteidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ if (*str == NUL) {
+ return 1;
+ }
+
+ return mb_charlen_len(str, byteidx + 1);
+}
+
+/// Convert the specified character index of line 'lnum' in buffer 'buf' to a
+/// byte index. Works only for loaded buffers. Returns -1 on failure. The index
+/// of the first byte and the first character is one.
+int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx)
+{
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return -1;
+ }
+
+ if (lnum > buf->b_ml.ml_line_count) {
+ lnum = buf->b_ml.ml_line_count;
+ }
+
+ char_u *str = ml_get_buf(buf, lnum, false);
+
+ // Convert the character offset to a byte offset
+ char_u *t = str;
+ while (*t != NUL && --charidx > 0) {
+ t += utfc_ptr2len(t);
+ }
+
+ return t - str + 1;
+}
+
/// Translate a VimL object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
@@ -8159,9 +8235,11 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl)
/// @param[in] tv Object to translate.
/// @param[in] dollar_lnum True when "$" is last line.
/// @param[out] ret_fnum Set to fnum for marks.
+/// @param[in] charcol True to return character column.
///
/// @return Pointer to position or NULL in case of error (e.g. invalid type).
-pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum)
+pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum,
+ const bool charcol)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
static pos_T pos;
@@ -8191,7 +8269,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
if (error) {
return NULL;
}
- len = (long)STRLEN(ml_get(pos.lnum));
+ if (charcol) {
+ len = mb_charlen(ml_get(pos.lnum));
+ } else {
+ len = STRLEN(ml_get(pos.lnum));
+ }
// We accept "$" for the column number: last column.
li = tv_list_find(l, 1L);
@@ -8222,19 +8304,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
return NULL;
}
if (name[0] == '.') { // Cursor.
- return &curwin->w_cursor;
+ pos = curwin->w_cursor;
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1;
+ }
+ return &pos;
}
if (name[0] == 'v' && name[1] == NUL) { // Visual start.
if (VIsual_active) {
- return &VIsual;
+ pos = VIsual;
+ } else {
+ pos = curwin->w_cursor;
+ }
+ if (charcol) {
+ pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col) - 1;
}
- return &curwin->w_cursor;
+ return &pos;
}
if (name[0] == '\'') { // Mark.
pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum);
if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) {
return NULL;
}
+ if (charcol) {
+ pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col) - 1;
+ }
return pp;
}
@@ -8260,22 +8354,24 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.col = 0;
} else {
pos.lnum = curwin->w_cursor.lnum;
- pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ if (charcol) {
+ pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr());
+ } else {
+ pos.col = (colnr_T)STRLEN(get_cursor_line_ptr());
+ }
}
return &pos;
}
return NULL;
}
-/*
- * Convert list in "arg" into a position and optional file number.
- * When "fnump" is NULL there is no file number, only 3 items.
- * Note that the column is passed on as-is, the caller may want to decrement
- * it to use 1 for the first column.
- * Return FAIL when conversion is not possible, doesn't check the position for
- * validity.
- */
-int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
+/// Convert list in "arg" into a position and optional file number.
+/// When "fnump" is NULL there is no file number, only 3 items.
+/// Note that the column is passed on as-is, the caller may want to decrement
+/// it to use 1 for the first column.
+/// Return FAIL when conversion is not possible, doesn't check the position for
+/// validity.
+int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol)
{
list_T *l;
long i = 0;
@@ -8311,6 +8407,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp)
if (n < 0) {
return FAIL;
}
+ // If character position is specified, then convert to byte position
+ if (charcol) {
+ // Get the text for the specified line in a loaded buffer
+ buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump);
+ if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
+ return FAIL;
+ }
+ n = buf_charidx_to_byteidx(buf, posp->lnum, n);
+ }
posp->col = n;
n = tv_list_find_nr(l, i, NULL); // off
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index ef2b8a0cc0..5c85d764fb 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -71,6 +71,7 @@ return {
chanclose={args={1, 2}},
chansend={args=2},
char2nr={args={1, 2}, base=1},
+ charcol={args=1, base=1},
charidx={args={2, 3}, base=1},
chdir={args=1, base=1},
cindent={args=1, base=1},
@@ -144,6 +145,7 @@ return {
getchangelist={args={0, 1}, base=1},
getchar={args={0, 1}},
getcharmod={},
+ getcharpos={args=1, base=1},
getcharsearch={},
getcharstr={args={0, 1}},
getcmdline={},
@@ -151,7 +153,8 @@ return {
getcmdtype={},
getcmdwintype={},
getcompletion={args={2, 3}, base=1},
- getcurpos={},
+ getcurpos={args={0, 1}, base=1},
+ getcursorcharpos={args={0, 1}, base=1},
getcwd={args={0, 2}, base=1},
getenv={args=1, base=1},
getfontname={args={0, 1}},
@@ -312,8 +315,10 @@ return {
serverstop={args=1},
setbufline={args=3, base=3},
setbufvar={args=3, base=3},
+ setcharpos={args=2, base=2},
setcharsearch={args=1, base=1},
setcmdpos={args=1, base=1},
+ setcursorcharpos={args={1, 3}, base=1},
setenv={args=2, base=2},
setfperm={args=2, base=1},
setline={args=2, base=2},
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index b96ff50787..edf6ed3c12 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1020,6 +1020,49 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0]));
}
+/// Get the current cursor column and store it in 'rettv'. If 'charcol' is true,
+/// returns the character index of the column. Otherwise, returns the byte index
+/// of the column.
+static void get_col(typval_T *argvars, typval_T *rettv, bool charcol)
+{
+ colnr_T col = 0;
+ pos_T *fp;
+ int fnum = curbuf->b_fnum;
+
+ fp = var2fpos(&argvars[0], false, &fnum, charcol);
+ if (fp != NULL && fnum == curbuf->b_fnum) {
+ if (fp->col == MAXCOL) {
+ // '> can be MAXCOL, get the length of the line then
+ if (fp->lnum <= curbuf->b_ml.ml_line_count) {
+ col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
+ } else {
+ col = MAXCOL;
+ }
+ } else {
+ col = fp->col + 1;
+ // col(".") when the cursor is on the NUL at the end of the line
+ // because of "coladd" can be seen as an extra column.
+ if (virtual_active() && fp == &curwin->w_cursor) {
+ char_u *p = get_cursor_pos_ptr();
+ if (curwin->w_cursor.coladd >=
+ (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) {
+ int l;
+ if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
+ col += l;
+ }
+ }
+ }
+ }
+ }
+ rettv->vval.v_number = col;
+}
+
+/// "charcol()" function
+static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ get_col(argvars, rettv, true);
+}
+
// "charidx()" function
static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -1148,45 +1191,10 @@ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "col(string)" function
- */
+/// "col(string)" function
static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- colnr_T col = 0;
- pos_T *fp;
- int fnum = curbuf->b_fnum;
-
- fp = var2fpos(&argvars[0], FALSE, &fnum);
- if (fp != NULL && fnum == curbuf->b_fnum) {
- if (fp->col == MAXCOL) {
- // '> can be MAXCOL, get the length of the line then
- if (fp->lnum <= curbuf->b_ml.ml_line_count) {
- col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1;
- } else {
- col = MAXCOL;
- }
- } else {
- col = fp->col + 1;
- // col(".") when the cursor is on the NUL at the end of the line
- // because of "coladd" can be seen as an extra column.
- if (virtual_active() && fp == &curwin->w_cursor) {
- char_u *p = get_cursor_pos_ptr();
-
- if (curwin->w_cursor.coladd
- >= (colnr_T)win_chartabsize(curwin, p,
- (curwin->w_virtcol
- - curwin->w_cursor.coladd))) {
- int l;
-
- if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) {
- col += l;
- }
- }
- }
- }
- }
- rettv->vval.v_number = col;
+ get_col(argvars, rettv, false);
}
/*
@@ -1549,24 +1557,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = ctx_size();
}
-/// "cursor(lnum, col)" function, or
-/// "cursor(list)"
-///
-/// Moves the cursor to the specified line and column.
-///
-/// @returns 0 when the position could be set, -1 otherwise.
-static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+/// Set the cursor position.
+/// If 'charcol' is true, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
{
long line, col;
long coladd = 0;
bool set_curswant = true;
rettv->vval.v_number = -1;
- if (argvars[1].v_type == VAR_UNKNOWN) {
+ if (argvars[0].v_type == VAR_LIST) {
pos_T pos;
colnr_T curswant = -1;
- if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) {
+ if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) {
emsg(_(e_invarg));
return;
}
@@ -1578,16 +1583,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
curwin->w_curswant = curswant - 1;
set_curswant = false;
}
- } else {
+ } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING)
+ && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) {
line = tv_get_lnum(argvars);
col = (long)tv_get_number_chk(&argvars[1], NULL);
+ if (charcol) {
+ col = buf_charidx_to_byteidx(curbuf, line, col);
+ }
if (argvars[2].v_type != VAR_UNKNOWN) {
coladd = (long)tv_get_number_chk(&argvars[2], NULL);
}
+ } else {
+ emsg(_(e_invarg));
+ return;
}
- if (line < 0 || col < 0
- || coladd < 0) {
- return; // type error; errmsg already given
+ if (line < 0 || col < 0 || coladd < 0) {
+ return; // type error; errmsg already given
}
if (line > 0) {
curwin->w_cursor.lnum = line;
@@ -1606,6 +1617,16 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
}
+/// "cursor(lnum, col)" function, or
+/// "cursor(list)"
+///
+/// Moves the cursor to the specified line and column.
+/// Returns 0 when the position could be set, -1 otherwise.
+static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, false);
+}
+
// "debugbreak()" function
static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -3288,6 +3309,67 @@ static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = mod_mask;
}
+static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol)
+{
+ pos_T *fp = NULL;
+ pos_T pos;
+ win_T *wp = curwin;
+ int fnum = -1;
+
+ if (getcurpos) {
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ wp = find_win_by_nr_or_id(&argvars[0]);
+ if (wp != NULL) {
+ fp = &wp->w_cursor;
+ }
+ } else {
+ fp = &curwin->w_cursor;
+ }
+ if (fp != NULL && charcol) {
+ pos = *fp;
+ pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col) - 1;
+ fp = &pos;
+ }
+ } else {
+ fp = var2fpos(&argvars[0], true, &fnum, charcol);
+ }
+
+ list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos);
+ tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
+ tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0));
+ tv_list_append_number(l, ((fp != NULL)
+ ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
+ : (varnumber_T)0));
+ tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
+ if (getcurpos) {
+ const int save_set_curswant = curwin->w_set_curswant;
+ const colnr_T save_curswant = curwin->w_curswant;
+ const colnr_T save_virtcol = curwin->w_virtcol;
+
+ if (wp == curwin) {
+ update_curswant();
+ }
+ tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL)
+ ? (varnumber_T)MAXCOL
+ : (varnumber_T)wp->w_curswant + 1));
+
+ // Do not change "curswant", as it is unexpected that a get
+ // function has a side effect.
+ if (wp == curwin && save_set_curswant) {
+ curwin->w_set_curswant = save_set_curswant;
+ curwin->w_curswant = save_curswant;
+ curwin->w_virtcol = save_virtcol;
+ curwin->w_valid &= ~VALID_VIRTCOL;
+ }
+ }
+}
+
+/// "getcharpos()" function
+static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ getpos_both(argvars, rettv, false, true);
+}
+
/*
* "getcharsearch()" function
*/
@@ -3843,61 +3925,21 @@ static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = os_get_pid();
}
-static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos)
+/// "getcurpos(string)" function
+static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T *fp;
- int fnum = -1;
-
- if (getcurpos) {
- fp = &curwin->w_cursor;
- } else {
- fp = var2fpos(&argvars[0], true, &fnum);
- }
-
- list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos));
- tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0);
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)fp->lnum
- : (varnumber_T)0));
- tv_list_append_number(l, ((fp != NULL)
- ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1)
- : (varnumber_T)0));
- tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0);
- if (getcurpos) {
- const int save_set_curswant = curwin->w_set_curswant;
- const colnr_T save_curswant = curwin->w_curswant;
- const colnr_T save_virtcol = curwin->w_virtcol;
-
- update_curswant();
- tv_list_append_number(l, (curwin->w_curswant == MAXCOL
- ? (varnumber_T)MAXCOL
- : (varnumber_T)curwin->w_curswant + 1));
-
- // Do not change "curswant", as it is unexpected that a get
- // function has a side effect.
- if (save_set_curswant) {
- curwin->w_set_curswant = save_set_curswant;
- curwin->w_curswant = save_curswant;
- curwin->w_virtcol = save_virtcol;
- curwin->w_valid &= ~VALID_VIRTCOL;
- }
- }
+ getpos_both(argvars, rettv, true, false);
}
-/*
- * "getcurpos(string)" function
- */
-static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, true);
+ getpos_both(argvars, rettv, true, true);
}
-/*
- * "getpos(string)" function
- */
+/// "getpos(string)" function
static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- getpos_both(argvars, rettv, false);
+ getpos_both(argvars, rettv, false, false);
}
/// "getqflist()" functions
@@ -5881,13 +5923,13 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr)
switchwin_T switchwin;
if (switch_win_noblock(&switchwin, wp, tp, true) == OK) {
check_cursor();
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
restore_win_noblock(&switchwin, true);
}
} else {
// use current window
- fp = var2fpos(&argvars[0], true, &fnum);
+ fp = var2fpos(&argvars[0], true, &fnum, false);
}
if (fp != NULL) {
@@ -8992,6 +9034,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// Set the cursor or mark position.
+/// If 'charpos' is TRUE, then use the column number as a character offet.
+/// Otherwise use the column number as a byte offset.
+static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
+{
+ pos_T pos;
+ int fnum;
+ colnr_T curswant = -1;
+
+ rettv->vval.v_number = -1;
+ const char *const name = tv_get_string_chk(argvars);
+ if (name != NULL) {
+ if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) {
+ if (pos.col != MAXCOL && --pos.col < 0) {
+ pos.col = 0;
+ }
+ if (name[0] == '.' && name[1] == NUL) {
+ // set cursor; "fnum" is ignored
+ curwin->w_cursor = pos;
+ if (curswant >= 0) {
+ curwin->w_curswant = curswant - 1;
+ curwin->w_set_curswant = false;
+ }
+ check_cursor();
+ rettv->vval.v_number = 0;
+ } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
+ // set mark
+ if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
+ rettv->vval.v_number = 0;
+ }
+ } else {
+ emsg(_(e_invarg));
+ }
+ }
+ }
+}
+
+/// "setcharpos()" function
+static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_position(argvars, rettv, true);
+}
+
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
dict_T *d;
@@ -9034,6 +9119,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+/// "setcursorcharpos" function
+static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ set_cursorpos(argvars, rettv, true);
+}
+
/// "setenv()" function
static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
@@ -9290,41 +9381,10 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/*
- * "setpos()" function
- */
+/// "setpos()" function
static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- pos_T pos;
- int fnum;
- colnr_T curswant = -1;
-
- rettv->vval.v_number = -1;
- const char *const name = tv_get_string_chk(argvars);
- if (name != NULL) {
- if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) {
- if (pos.col != MAXCOL && --pos.col < 0) {
- pos.col = 0;
- }
- if (name[0] == '.' && name[1] == NUL) {
- // set cursor; "fnum" is ignored
- curwin->w_cursor = pos;
- if (curswant >= 0) {
- curwin->w_curswant = curswant - 1;
- curwin->w_set_curswant = false;
- }
- check_cursor();
- rettv->vval.v_number = 0;
- } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) {
- // set mark
- if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) {
- rettv->vval.v_number = 0;
- }
- } else {
- emsg(_(e_invarg));
- }
- }
- }
+ set_position(argvars, rettv, false);
}
/*
@@ -12007,7 +12067,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr)
pos_T *fp;
int fnum = curbuf->b_fnum;
- fp = var2fpos(&argvars[0], FALSE, &fnum);
+ fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
// Limit the column to a valid value, getvvcol() doesn't check.
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 42ac1839e6..e583ce49b2 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -3091,7 +3091,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL);
if (lnum == 0) { // No valid number, try using same function as line() does.
int fnum;
- pos_T *const fp = var2fpos(tv, true, &fnum);
+ pos_T *const fp = var2fpos(tv, true, &fnum, false);
if (fp != NULL) {
lnum = fp->lnum;
}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 1916fa7e44..3b3d4e50cc 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -3652,8 +3652,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle
// We do it here once to avoid it to be replaced over and over again.
// But don't do it when it starts with "\=", then it's an expression.
assert(sub != NULL);
+
+ bool sub_needs_free = false;
if (!(sub[0] == '\\' && sub[1] == '=')) {
+ char_u *source = sub;
sub = regtilde(sub, p_magic);
+ // When previewing, the new pattern allocated by regtilde() needs to be freed
+ // in this function because it will not be used or freed by regtilde() later.
+ sub_needs_free = preview && sub != source;
}
// Check for a match on each line.
@@ -4450,6 +4456,10 @@ skip:
kv_destroy(preview_lines.subresults);
+ if (sub_needs_free) {
+ xfree(sub);
+ }
+
return preview_buf;
#undef ADJUST_SUB_FIRSTLNUM
#undef PUSH_PREVIEW_LINES
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index ba234bc841..6a6c915094 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -6534,11 +6534,16 @@ char_u *regtilde(char_u *source, int magic)
}
}
- xfree(reg_prev_sub);
- if (newsub != source) /* newsub was allocated, just keep it */
- reg_prev_sub = newsub;
- else /* no ~ found, need to save newsub */
- reg_prev_sub = vim_strsave(newsub);
+ // Only change reg_prev_sub when not previewing.
+ if (!(State & CMDPREVIEW)) {
+ xfree(reg_prev_sub);
+ if (newsub != source) { // newsub was allocated, just keep it
+ reg_prev_sub = newsub;
+ } else { // no ~ found, need to save newsub
+ reg_prev_sub = vim_strsave(newsub);
+ }
+ }
+
return newsub;
}
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index a10a2a0c32..54d7e54fb4 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l)
if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
continue;
}
- if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) {
+ if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) {
continue;
}
if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true))
diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim
index e8c4a952ee..f2ffd50726 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/src/nvim/testdir/test_cursor_func.vim
@@ -1,4 +1,4 @@
-" Tests for cursor().
+" Tests for cursor() and other functions that get/set the cursor position
func Test_wrong_arguments()
call assert_fails('call cursor(1. 3)', 'E474:')
@@ -24,6 +24,9 @@ func Test_move_cursor()
" below last line goes to last line
call cursor(9, 1)
call assert_equal([4, 1, 0, 1], getcurpos()[1:])
+ " pass string arguments
+ call cursor('3', '3')
+ call assert_equal([3, 3, 0, 3], getcurpos()[1:])
call setline(1, ["\<TAB>"])
call cursor(1, 1, 1)
@@ -119,3 +122,188 @@ func Test_screenpos_number()
close
bwipe!
endfunc
+
+func SaveVisualStartCharPos()
+ call add(g:VisualStartPos, getcharpos('v'))
+ return ''
+endfunc
+
+" Test for the getcharpos() function
+func Test_getcharpos()
+ call assert_fails('call getcharpos({})', 'E731:')
+ call assert_equal([0, 0, 0, 0], getcharpos(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal([0, 1, 1, 0], getcharpos('.'))
+ call assert_equal([0, 4, 1, 0], getcharpos('$'))
+ normal 2G6l
+ call assert_equal([0, 2, 7, 0], getcharpos('.'))
+ normal 3G$
+ call assert_equal([0, 3, 1, 0], getcharpos('.'))
+ normal 4G$
+ call assert_equal([0, 4, 9, 0], getcharpos('.'))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal([0, 2, 8, 0], getcharpos("'m"))
+ delmarks m
+ call assert_equal([0, 0, 0, 0], getcharpos("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharPos()
+ let g:VisualStartPos = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([[0, 2, 7, 0], [0, 2, 9, 0], [0, 2, 5, 0]], g:VisualStartPos)
+ call assert_equal([0, 2, 9, 0], getcharpos('v'))
+ let g:VisualStartPos = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 3, 1, 0], [0, 3, 1, 0]], g:VisualStartPos)
+ let g:VisualStartPos = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([[0, 1, 1, 0], [0, 1, 1, 0]], g:VisualStartPos)
+ vunmap <F3>
+
+ %bw!
+endfunc
+
+" Test for the setcharpos() function
+func Test_setcharpos()
+ call assert_equal(-1, setcharpos('.', v:_null_list))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ call setcharpos('.', [0, 1, 1, 0])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 4, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 3, 1, 0])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcharpos('.', [0, 4, 20, 0])
+ call assert_equal([4, 9], [line('.'), col('.')])
+
+ " Test for mark
+ delmarks m
+ call setcharpos("'m", [0, 2, 9, 0])
+ normal `m
+ call assert_equal([2, 11], [line('.'), col('.')])
+
+ %bw!
+ call assert_equal(-1, setcharpos('.', [10, 3, 1, 0]))
+endfunc
+
+func SaveVisualStartCharCol()
+ call add(g:VisualStartCol, charcol('v'))
+ return ''
+endfunc
+
+" Test for the charcol() function
+func Test_charcol()
+ call assert_fails('call charcol({})', 'E731:')
+ call assert_equal(0, charcol(0))
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+
+ " Test for '.' and '$'
+ normal 1G
+ call assert_equal(1, charcol('.'))
+ call assert_equal(1, charcol('$'))
+ normal 2G6l
+ call assert_equal(7, charcol('.'))
+ call assert_equal(10, charcol('$'))
+ normal 3G$
+ call assert_equal(1, charcol('.'))
+ call assert_equal(2, charcol('$'))
+ normal 4G$
+ call assert_equal(9, charcol('.'))
+ call assert_equal(10, charcol('$'))
+
+ " Test for [lnum, '$']
+ call assert_equal(1, charcol([1, '$']))
+ call assert_equal(10, charcol([2, '$']))
+ call assert_equal(2, charcol([3, '$']))
+ call assert_equal(0, charcol([5, '$']))
+
+ " Test for a mark
+ normal 2G7lmmgg
+ call assert_equal(8, charcol("'m"))
+ delmarks m
+ call assert_equal(0, charcol("'m"))
+
+ " Test for the visual start column
+ vnoremap <expr> <F3> SaveVisualStartCharCol()
+ let g:VisualStartCol = []
+ exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>"
+ call assert_equal([7, 9, 5], g:VisualStartCol)
+ call assert_equal(9, charcol('v'))
+ let g:VisualStartCol = []
+ exe "normal 3Gv$\<F3>o\<F3>"
+ call assert_equal([1, 1], g:VisualStartCol)
+ let g:VisualStartCol = []
+ exe "normal 1Gv$\<F3>o\<F3>"
+ call assert_equal([1, 1], g:VisualStartCol)
+ vunmap <F3>
+
+ %bw!
+endfunc
+
+" Test for getcursorcharpos()
+func Test_getcursorcharpos()
+ call assert_equal(getcursorcharpos(), getcursorcharpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999))
+
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal 1G9l
+ call assert_equal([0, 1, 1, 0, 1], getcursorcharpos())
+ normal 2G9l
+ call assert_equal([0, 2, 9, 0, 14], getcursorcharpos())
+ normal 3G9l
+ call assert_equal([0, 3, 1, 0, 1], getcursorcharpos())
+ normal 4G9l
+ call assert_equal([0, 4, 9, 0, 9], getcursorcharpos())
+
+ let winid = win_getid()
+ normal 2G5l
+ wincmd w
+ call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid))
+ %bw!
+endfunc
+
+" Test for setcursorcharpos()
+func Test_setcursorcharpos()
+ call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:')
+ call assert_fails('call setcursorcharpos([1])', 'E474:')
+ call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:')
+ new
+ call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678'])
+ normal G
+ call setcursorcharpos([1, 1])
+ call assert_equal([1, 1], [line('.'), col('.')])
+ call setcursorcharpos([2, 7, 0])
+ call assert_equal([2, 9], [line('.'), col('.')])
+ call setcursorcharpos(3, 4)
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([3, 1])
+ call assert_equal([3, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 0, 0, 0])
+ call assert_equal([4, 1], [line('.'), col('.')])
+ call setcursorcharpos([4, 20])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos([100, 100, 100, 100])
+ call assert_equal([4, 9], [line('.'), col('.')])
+ normal 1G
+ call setcursorcharpos('$', 1)
+ call assert_equal([4, 1], [line('.'), col('.')])
+
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 883ba5de3d..95eccde35c 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -65,11 +65,9 @@ func Test_E963()
endfunc
func Test_for_invalid()
- " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that
- " patch cannot be used until v8.2.2658 is ported (for loop over Strings)
- call assert_fails("for x in 99", 'E897:')
- call assert_fails("for x in function('winnr')", 'E897:')
- call assert_fails("for x in {'a': 9}", 'E897:')
+ call assert_fails("for x in 99", 'E1098:')
+ call assert_fails("for x in function('winnr')", 'E1098:')
+ call assert_fails("for x in {'a': 9}", 'E1098:')
if 0
/1/5/2/s/\n
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index d4e5563865..4ef35b3a46 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -731,101 +731,126 @@ func Test_setfiletype_completion()
call assert_equal('"setfiletype java javacc javascript javascriptreact', @:)
endfunc
-func Test_hook_file()
+"""""""""""""""""""""""""""""""""""""""""""""""""
+" Tests for specific extentions and filetypes.
+" Keep sorted.
+"""""""""""""""""""""""""""""""""""""""""""""""""
+
+func Test_bas_file()
filetype on
- call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
- split Xfile.hook
- call assert_equal('dosini', &filetype)
+ call writefile(['looks like BASIC'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('basic', &filetype)
bwipe!
- call writefile(['not pacman'], 'Xfile.hook')
- split Xfile.hook
- call assert_notequal('dosini', &filetype)
+ " Test dist#ft#FTbas()
+
+ let g:filetype_bas = 'freebasic'
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
+ unlet g:filetype_bas
- call delete('Xfile.hook')
- filetype off
-endfunc
+ " FreeBASIC
-func Test_tf_file()
- filetype on
+ call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
- call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
- split Xfile.tf
- call assert_equal('tf', &filetype)
+ call writefile(['#define TESTING'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call writefile(['provider "azurerm" {'], 'Xfile.tf')
- split Xfile.tf
- call assert_equal('terraform', &filetype)
+ call writefile(['option byval'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
bwipe!
- call delete('Xfile.tf')
- filetype off
-endfunc
+ call writefile(['extern "C"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('freebasic', &filetype)
+ bwipe!
+ " QB64
-func Test_ts_file()
- filetype on
+ call writefile(['$LET TESTING = 1'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
+ bwipe!
- call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('xml', &filetype)
+ call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('qb64', &filetype)
bwipe!
- call writefile(['// looks like Typescript'], 'Xfile.ts')
- split Xfile.ts
- call assert_equal('typescript', &filetype)
+ " Visual Basic
+
+ call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas')
+ split Xfile.bas
+ call assert_equal('vb', &filetype)
bwipe!
- call delete('Xfile.hook')
+ call delete('Xfile.bas')
filetype off
endfunc
-func Test_ttl_file()
+func Test_dep3patch_file()
filetype on
- call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('turtle', &filetype)
- bwipe!
+ call assert_true(mkdir('debian/patches', 'p'))
- call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
- split Xfile.ttl
- call assert_equal('teraterm', &filetype)
+ " series files are not patches
+ call writefile(['Description: some awesome patch'], 'debian/patches/series')
+ split debian/patches/series
+ call assert_notequal('dep3patch', &filetype)
bwipe!
- call delete('Xfile.ttl')
- filetype off
-endfunc
+ " diff/patch files without the right headers should still show up as ft=diff
+ call writefile([], 'debian/patches/foo.diff')
+ split debian/patches/foo.diff
+ call assert_equal('diff', &filetype)
+ bwipe!
-func Test_pp_file()
- filetype on
+ " Files with the right headers are detected as dep3patch, even if they don't
+ " have a diff/patch extension
+ call writefile(['Subject: dep3patches'], 'debian/patches/bar')
+ split debian/patches/bar
+ call assert_equal('dep3patch', &filetype)
+ bwipe!
- call writefile(['looks like puppet'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('puppet', &filetype)
+ " Files in sub-directories are detected
+ call assert_true(mkdir('debian/patches/s390x', 'p'))
+ call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
+ split debian/patches/s390x/bar
+ call assert_equal('dep3patch', &filetype)
bwipe!
- let g:filetype_pp = 'pascal'
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ " The detection stops when seeing the "header end" marker
+ call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
+ split debian/patches/baz
+ call assert_notequal('dep3patch', &filetype)
bwipe!
- unlet g:filetype_pp
- " Test dist#ft#FTpp()
- call writefile(['{ pascal comment'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ call delete('debian', 'rf')
+endfunc
+
+func Test_dsl_file()
+ filetype on
+
+ call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('dsl', &filetype)
bwipe!
- call writefile(['procedure pascal'], 'Xfile.pp')
- split Xfile.pp
- call assert_equal('pascal', &filetype)
+ call writefile(['workspace {'], 'dslfile.dsl')
+ split dslfile.dsl
+ call assert_equal('structurizr', &filetype)
bwipe!
- call delete('Xfile.pp')
+ call delete('dslfile.dsl')
filetype off
endfunc
@@ -866,20 +891,183 @@ func Test_ex_file()
filetype off
endfunc
-func Test_dsl_file()
+func Test_foam_file()
filetype on
+ call assert_true(mkdir('0', 'p'))
+ call assert_true(mkdir('0.orig', 'p'))
- call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('dsl', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
+ split Xfile1Dict
+ call assert_equal('foam', &filetype)
bwipe!
- call writefile(['workspace {'], 'dslfile.dsl')
- split dslfile.dsl
- call assert_equal('structurizr', &filetype)
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
+ split Xfile1Dict.something
+ call assert_equal('foam', &filetype)
bwipe!
- call delete('dslfile.dsl')
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ split XfileProperties
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ split XfileProperties.something
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0/Xfile')
+ split 0/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
+ split 0.orig/Xfile
+ call assert_equal('foam', &filetype)
+ bwipe!
+
+ call delete('0', 'rf')
+ call delete('0.orig', 'rf')
+ call delete('Xfile1Dict')
+ call delete('Xfile1Dict.something')
+ call delete('XfileProperties')
+ call delete('XfileProperties.something')
+ filetype off
+endfunc
+
+func Test_frm_file()
+ filetype on
+
+ call writefile(['looks like FORM'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+
+ " Test dist#ft#FTfrm()
+
+ let g:filetype_frm = 'form'
+ split Xfile.frm
+ call assert_equal('form', &filetype)
+ bwipe!
+ unlet g:filetype_frm
+
+ " Visual Basic
+
+ call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
+ split Xfile.frm
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ call delete('Xfile.frm')
+ filetype off
+endfunc
+
+func Test_fs_file()
+ filetype on
+
+ call writefile(['looks like F#'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('fsharp', &filetype)
+ bwipe!
+
+ let g:filetype_fs = 'forth'
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+ unlet g:filetype_fs
+
+ " Test dist#ft#FTfs()
+
+ " Forth (Gforth)
+
+ call writefile(['( Forth inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\ Forth line comment'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ " empty line comment - no space required
+ call writefile(['\'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\G Forth documentation comment '], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
+ split Xfile.fs
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call delete('Xfile.fs')
+ filetype off
+endfunc
+
+func Test_git_file()
+ filetype on
+
+ call assert_true(mkdir('Xrepo.git', 'p'))
+
+ call writefile([], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
+ split Xrepo.git/HEAD
+ call assert_equal('git', &filetype)
+ bwipe!
+
+ call delete('Xrepo.git', 'rf')
+ filetype off
+endfunc
+
+func Test_hook_file()
+ filetype on
+
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_equal('dosini', &filetype)
+ bwipe!
+
+ call writefile(['not pacman'], 'Xfile.hook')
+ split Xfile.hook
+ call assert_notequal('dosini', &filetype)
+ bwipe!
+
+ call delete('Xfile.hook')
filetype off
endfunc
@@ -983,111 +1171,6 @@ func Test_m_file()
filetype off
endfunc
-func Test_xpm_file()
- filetype on
-
- call writefile(['this is XPM2'], 'file.xpm')
- split file.xpm
- call assert_equal('xpm2', &filetype)
- bwipe!
-
- call delete('file.xpm')
- filetype off
-endfunc
-
-func Test_fs_file()
- filetype on
-
- call writefile(['looks like F#'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('fsharp', &filetype)
- bwipe!
-
- let g:filetype_fs = 'forth'
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
- unlet g:filetype_fs
-
- " Test dist#ft#FTfs()
-
- " Forth (Gforth)
-
- call writefile(['( Forth inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['\ Forth line comment'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- " empty line comment - no space required
- call writefile(['\'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile(['\G Forth documentation comment '], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
- split Xfile.fs
- call assert_equal('forth', &filetype)
- bwipe!
-
- call delete('Xfile.fs')
- filetype off
-endfunc
-
-func Test_dep3patch_file()
- filetype on
-
- call assert_true(mkdir('debian/patches', 'p'))
-
- " series files are not patches
- call writefile(['Description: some awesome patch'], 'debian/patches/series')
- split debian/patches/series
- call assert_notequal('dep3patch', &filetype)
- bwipe!
-
- " diff/patch files without the right headers should still show up as ft=diff
- call writefile([], 'debian/patches/foo.diff')
- split debian/patches/foo.diff
- call assert_equal('diff', &filetype)
- bwipe!
-
- " Files with the right headers are detected as dep3patch, even if they don't
- " have a diff/patch extension
- call writefile(['Subject: dep3patches'], 'debian/patches/bar')
- split debian/patches/bar
- call assert_equal('dep3patch', &filetype)
- bwipe!
-
- " Files in sub-directories are detected
- call assert_true(mkdir('debian/patches/s390x', 'p'))
- call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar')
- split debian/patches/s390x/bar
- call assert_equal('dep3patch', &filetype)
- bwipe!
-
- " The detection stops when seeing the "header end" marker
- call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz')
- split debian/patches/baz
- call assert_notequal('dep3patch', &filetype)
- bwipe!
-
- call delete('debian', 'rf')
-endfunc
-
func Test_patch_file()
filetype on
@@ -1110,171 +1193,132 @@ func Test_patch_file()
filetype off
endfunc
-func Test_git_file()
+func Test_perl_file()
filetype on
- call assert_true(mkdir('Xrepo.git', 'p'))
-
- call writefile([], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('', &filetype)
- bwipe!
-
- call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
-
- call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
+ " only tests one case, should do more
+ let lines =<< trim END
- call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD')
- split Xrepo.git/HEAD
- call assert_equal('git', &filetype)
- bwipe!
+ use a
+ END
+ call writefile(lines, "Xfile.t")
+ split Xfile.t
+ call assert_equal('perl', &filetype)
+ bwipe
- call delete('Xrepo.git', 'rf')
+ call delete('Xfile.t')
filetype off
endfunc
-func Test_foam_file()
+func Test_pp_file()
filetype on
- call assert_true(mkdir('0', 'p'))
- call assert_true(mkdir('0.orig', 'p'))
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
- split Xfile1Dict
- call assert_equal('foam', &filetype)
+ call writefile(['looks like puppet'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('puppet', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
- split Xfile1Dict.something
- call assert_equal('foam', &filetype)
+ let g:filetype_pp = 'pascal'
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
+ unlet g:filetype_pp
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
- split XfileProperties
- call assert_equal('foam', &filetype)
+ " Test dist#ft#FTpp()
+ call writefile(['{ pascal comment'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
- split XfileProperties.something
- call assert_equal('foam', &filetype)
+ call writefile(['procedure pascal'], 'Xfile.pp')
+ split Xfile.pp
+ call assert_equal('pascal', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
- split XfileProperties
- call assert_equal('foam', &filetype)
- bwipe!
+ call delete('Xfile.pp')
+ filetype off
+endfunc
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
- split XfileProperties.something
- call assert_equal('foam', &filetype)
- bwipe!
+func Test_tex_file()
+ filetype on
- call writefile(['FoamFile {', ' object something;'], '0/Xfile')
- split 0/Xfile
- call assert_equal('foam', &filetype)
- bwipe!
+ " only tests one case, should do more
+ let lines =<< trim END
+ % This is a sentence.
- call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile')
- split 0.orig/Xfile
- call assert_equal('foam', &filetype)
- bwipe!
+ This is a sentence.
+ END
+ call writefile(lines, "Xfile.tex")
+ split Xfile.tex
+ call assert_equal('plaintex', &filetype)
+ bwipe
- call delete('0', 'rf')
- call delete('0.orig', 'rf')
+ call delete('Xfile.tex')
filetype off
endfunc
-func Test_bas_file()
+func Test_tf_file()
filetype on
- call writefile(['looks like BASIC'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('basic', &filetype)
+ call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('tf', &filetype)
bwipe!
- " Test dist#ft#FTbas()
-
- let g:filetype_bas = 'freebasic'
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
+ call writefile(['provider "azurerm" {'], 'Xfile.tf')
+ split Xfile.tf
+ call assert_equal('terraform', &filetype)
bwipe!
- unlet g:filetype_bas
- " FreeBASIC
-
- call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
+ call delete('Xfile.tf')
+ filetype off
+endfunc
- call writefile(['#define TESTING'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
- bwipe!
+func Test_ts_file()
+ filetype on
- call writefile(['option byval'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
+ call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('xml', &filetype)
bwipe!
- call writefile(['extern "C"'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('freebasic', &filetype)
+ call writefile(['// looks like Typescript'], 'Xfile.ts')
+ split Xfile.ts
+ call assert_equal('typescript', &filetype)
bwipe!
- " QB64
+ call delete('Xfile.ts')
+ filetype off
+endfunc
- call writefile(['$LET TESTING = 1'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('qb64', &filetype)
- bwipe!
+func Test_ttl_file()
+ filetype on
- call writefile(['OPTION _EXPLICIT'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('qb64', &filetype)
+ call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('turtle', &filetype)
bwipe!
- " Visual Basic
-
- call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas')
- split Xfile.bas
- call assert_equal('vb', &filetype)
+ call writefile(['looks like Tera Term Language'], 'Xfile.ttl')
+ split Xfile.ttl
+ call assert_equal('teraterm', &filetype)
bwipe!
- call delete('Xfile.bas')
+ call delete('Xfile.ttl')
filetype off
endfunc
-func Test_frm_file()
+func Test_xpm_file()
filetype on
- call writefile(['looks like FORM'], 'Xfile.frm')
- split Xfile.frm
- call assert_equal('form', &filetype)
- bwipe!
-
- " Test dist#ft#FTfrm()
-
- let g:filetype_frm = 'form'
- split Xfile.frm
- call assert_equal('form', &filetype)
- bwipe!
- unlet g:filetype_frm
-
- " Visual Basic
-
- call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
- split Xfile.frm
- call assert_equal('vb', &filetype)
+ call writefile(['this is XPM2'], 'file.xpm')
+ split file.xpm
+ call assert_equal('xpm2', &filetype)
bwipe!
- call delete('Xfile.frm')
+ call delete('file.xpm')
filetype off
endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 42facdd491..6c2b0b97b6 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -1712,6 +1712,33 @@ func Test_nr2char()
call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
endfunc
+" Test for getcurpos() and setpos()
+func Test_getcurpos_setpos()
+ new
+ call setline(1, ['012345678', '012345678'])
+ normal gg6l
+ let sp = getcurpos()
+ normal 0
+ call setpos('.', sp)
+ normal jyl
+ call assert_equal('6', @")
+ call assert_equal(-1, setpos('.', v:_null_list))
+ call assert_equal(-1, setpos('.', {}))
+
+ let winid = win_getid()
+ normal G$
+ let pos = getcurpos()
+ wincmd w
+ call assert_equal(pos, getcurpos(winid))
+
+ wincmd w
+ close!
+
+ call assert_equal(getcurpos(), getcurpos(0))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(-1))
+ call assert_equal([0, 0, 0, 0, 0], getcurpos(1999))
+endfunc
+
func HasDefault(msg = 'msg')
return a:msg
endfunc
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index 34733f127f..c59cab5f36 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1692,6 +1692,26 @@ func Test_function_defined_line()
call delete('Xtest.vim')
endfunc
+func Test_for_over_string()
+ let res = ''
+ for c in 'aéc̀d'
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('a-é-c̀-d-', res)
+
+ let res = ''
+ for c in ''
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+
+ let res = ''
+ for c in v:_null_string
+ let res ..= c .. '-'
+ endfor
+ call assert_equal('', res)
+endfunc
+
"-------------------------------------------------------------------------------
" Modelines {{{1
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index b6e2f2311f..10700d9508 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -293,6 +293,70 @@ describe(":substitute, 'inccommand' preserves", function()
end)
end
+ for _, case in ipairs({'', 'split', 'nosplit'}) do
+ it('previous substitute string ~ (inccommand='..case..') #12109', function()
+ local screen = Screen.new(30,10)
+ common_setup(screen, case, default_text)
+
+ feed(':%s/Inc/SUB<CR>')
+ expect([[
+ SUB substitution on
+ two lines
+ ]])
+
+ feed(':%s/line/')
+ poke_eventloop()
+ feed('~')
+ poke_eventloop()
+ feed('<CR>')
+ expect([[
+ SUB substitution on
+ two SUBs
+ ]])
+
+ feed(':%s/sti/')
+ poke_eventloop()
+ feed('~')
+ poke_eventloop()
+ feed('B')
+ poke_eventloop()
+ feed('<CR>')
+ expect([[
+ SUB subSUBBtution on
+ two SUBs
+ ]])
+
+ feed(':%s/ion/NEW<CR>')
+ expect([[
+ SUB subSUBBtutNEW on
+ two SUBs
+ ]])
+
+ feed(':%s/two/')
+ poke_eventloop()
+ feed('N')
+ poke_eventloop()
+ feed('~')
+ poke_eventloop()
+ feed('<CR>')
+ expect([[
+ SUB subSUBBtutNEW on
+ NNEW SUBs
+ ]])
+
+ feed(':%s/bS/')
+ poke_eventloop()
+ feed('~')
+ poke_eventloop()
+ feed('W')
+ poke_eventloop()
+ feed('<CR>')
+ expect([[
+ SUB suNNEWWUBBtutNEW on
+ NNEW SUBs
+ ]])
+ end)
+ end
end)
describe(":substitute, 'inccommand' preserves undo", function()