diff options
-rw-r--r-- | runtime/autoload/health/provider.vim | 2 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 38 | ||||
-rw-r--r-- | runtime/doc/nvim_terminal_emulator.txt | 7 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 8 | ||||
-rw-r--r-- | runtime/lua/vim/uri.lua | 15 | ||||
-rw-r--r-- | src/nvim/eval.c | 4 | ||||
-rw-r--r-- | src/nvim/eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 97 | ||||
-rw-r--r-- | src/nvim/lua/vim.lua | 12 | ||||
-rw-r--r-- | src/nvim/option.c | 5 | ||||
-rw-r--r-- | src/nvim/tag.c | 2 | ||||
-rw-r--r-- | src/nvim/terminal.c | 8 | ||||
-rw-r--r-- | src/nvim/testdir/test_functions.vim | 30 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 22 | ||||
-rw-r--r-- | test/functional/lua/uri_spec.lua | 11 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 102 | ||||
-rw-r--r-- | test/functional/terminal/buffer_spec.lua | 12 |
17 files changed, 350 insertions, 26 deletions
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 86f9f060ff..6d481e9f49 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -589,7 +589,7 @@ function! s:check_ruby() abort \ 'Are you behind a firewall or proxy?']) return endif - let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 1, 'not found') + let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found') let current_gem_cmd = host .' --version' let current_gem = s:system(current_gem_cmd) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 4ff6269004..6f13b34876 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -6689,6 +6689,33 @@ range({expr} [, {max} [, {stride}]]) *range()* range(0) " [] range(2, 0) " error! < + *readdir()* +readdir({directory} [, {expr}]) + Return a list with file and directory names in {directory}. + + When {expr} is omitted all entries are included. + When {expr} is given, it is evaluated to check what to do: + If {expr} results in -1 then no further entries will + be handled. + If {expr} results in 0 then this entry will not be + added to the list. + If {expr} results in 1 then this entry will be added + to the list. + Each time {expr} is evaluated |v:val| is set to the entry name. + When {expr} is a function the name is passed as the argument. + For example, to get a list of files ending in ".txt": > + readdir(dirname, {n -> n =~ '.txt$'}) +< To skip hidden and backup files: > + readdir(dirname, {n -> n !~ '^\.\|\~$'}) + +< If you want to get a directory tree: > + function! s:tree(dir) + return {a:dir : map(readdir(a:dir), + \ {_, x -> isdirectory(x) ? + \ {x : s:tree(a:dir . '/' . x)} : x})} + endfunction + echo s:tree(".") +< *readfile()* readfile({fname} [, {binary} [, {max}]]) Read file {fname} and return a |List|, each line of the file @@ -6720,17 +6747,6 @@ readfile({fname} [, {binary} [, {max}]]) the result is an empty list. Also see |writefile()|. - *readdir()* -readdir({directory} [, {expr}]) - Return a list with file and directory names in {directory}. - You can also use |glob()| if you don't need to do complicated - things, such as limiting the number of matches. - - When {expr} is omitted all entries are included. - When {expr} is given, it is evaluated to check what to do: - If {expr} results in -1 then no further entries will - be handled. - reg_executing() *reg_executing()* Returns the single letter name of the register being executed. Returns an empty string when no register is being executed. diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 55c5335a60..a96d118667 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -50,6 +50,13 @@ mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return to normal-mode. |CTRL-\_CTRL-N| +Terminal-mode forces these local options: + + 'nocursorline' + 'nocursorcolumn' + 'scrolloff' = 0 + 'sidescrolloff' = 0 + Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used to automate any terminal interaction. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index e77b9f199c..494eebf9ea 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -160,7 +160,7 @@ function M.apply_text_document_edit(text_document_edit) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) -- `VersionedTextDocumentIdentifier`s version may be nil https://microsoft.github.io/language-server-protocol/specification#versionedTextDocumentIdentifier - if text_document.version ~= nil and M.buf_versions[bufnr] > text_document.version then + if text_document.version ~= vim.NIL and M.buf_versions[bufnr] > text_document.version then print("Buffer ", text_document.uri, " newer than edits.") return end @@ -432,9 +432,9 @@ function M.jump_to_location(location) vim.cmd "normal! m'" -- Push a new item into tagstack - local items = {} - table.insert(items, {tagname=vim.fn.expand("<cword>"), from=vim.fn.getpos('.')}) - vim.fn.settagstack(vim.fn.bufnr('%'), {items=items}, 't') + local from = {vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0} + local items = {{tagname=vim.fn.expand('<cword>'), from=from}} + vim.fn.settagstack(vim.fn.win_getid(), {items=items}, 't') --- Jump to new location api.nvim_set_current_buf(bufnr) diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index d91fb7ffd3..e28cc9e20f 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -66,7 +66,13 @@ local function uri_from_fname(path) end local function uri_from_bufnr(bufnr) - return uri_from_fname(vim.api.nvim_buf_get_name(bufnr)) + local fname = vim.api.nvim_buf_get_name(bufnr) + local scheme = fname:match("^([a-z]+)://.*") + if scheme then + return fname + else + return uri_from_fname(fname) + end end local function uri_to_fname(uri) @@ -83,7 +89,12 @@ end -- Return or create a buffer for a uri. local function uri_to_bufnr(uri) - return vim.fn.bufadd((uri_to_fname(uri))) + local scheme = assert(uri:match("^([a-z]+)://.*"), 'Uri must contain a scheme: ' .. uri) + if scheme == 'file' then + return vim.fn.bufadd(uri_to_fname(uri)) + else + return vim.fn.bufadd(uri) + end end return { diff --git a/src/nvim/eval.c b/src/nvim/eval.c index a6162af472..4a0876a952 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -915,7 +915,7 @@ varnumber_T eval_to_number(char_u *expr) * Save the current typeval in "save_tv". * When not used yet add the variable to the v: hashtable. */ -static void prepare_vimvar(int idx, typval_T *save_tv) +void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) @@ -926,7 +926,7 @@ static void prepare_vimvar(int idx, typval_T *save_tv) * Restore v: variable "idx" to typeval "save_tv". * When no longer defined, remove the variable from the v: hashtable. */ -static void restore_vimvar(int idx, typval_T *save_tv) +void restore_vimvar(int idx, typval_T *save_tv) { hashitem_T *hi; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index d3e769a7ef..65c4cfe553 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -255,6 +255,7 @@ return { pyeval={args=1}, pyxeval={args=1}, range={args={1, 3}}, + readdir={args={1, 2}}, readfile={args={1, 3}}, reg_executing={}, reg_recording={}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index bbe6f6de9d..217490ad10 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2063,8 +2063,10 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } - } else + XFREE_CLEAR(result); + } else { rettv->vval.v_string = result; + } } else { // When the optional second argument is non-zero, don't remove matches // for 'wildignore' and don't put matches for 'suffixes' at the end. @@ -6314,6 +6316,99 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +// Evaluate "expr" for readdir(). +static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +{ + typval_T save_val; + typval_T rettv; + typval_T argv[2]; + varnumber_T retval = 0; + bool error = false; + + prepare_vimvar(VV_VAL, &save_val); + set_vim_var_string(VV_VAL, name, -1); + argv[0].v_type = VAR_STRING; + argv[0].vval.v_string = (char_u *)name; + + if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) { + goto theend; + } + + retval = tv_get_number_chk(&rettv, &error); + if (error) { + retval = -1; + } + + tv_clear(&rettv); + +theend: + set_vim_var_string(VV_VAL, NULL, 0); + restore_vimvar(VV_VAL, &save_val); + return retval; +} + +// "readdir()" function +static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + typval_T *expr; + const char *path; + garray_T ga; + Directory dir; + + tv_list_alloc_ret(rettv, kListLenUnknown); + path = tv_get_string(&argvars[0]); + expr = &argvars[1]; + ga_init(&ga, (int)sizeof(char *), 20); + + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + } else { + for (;;) { + bool ignore; + + path = os_scandir_next(&dir); + if (path == NULL) { + break; + } + + ignore = (path[0] == '.' + && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); + if (!ignore && expr->v_type != VAR_UNKNOWN) { + varnumber_T r = readdir_checkitem(expr, path); + + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(&ga, 1); + ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); + } + } + + os_closedir(&dir); + } + + rettv->vval.v_list = tv_list_alloc(kListLenShouldKnow); + if (rettv->vval.v_list != NULL) { + tv_list_ref(rettv->vval.v_list); + sort_strings((char_u **)ga.ga_data, ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + path = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, path, -1); + } + } + for (int i = 0; i < ga.ga_len; i++) { + xfree(((uint8_t **)ga.ga_data)[i]); + } + + ga_clear(&ga); +} + /* * "readfile()" function */ diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 8ba550ea31..fc337c5e7e 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -212,6 +212,18 @@ do vim.api.nvim_put(lines, 'c', true, true) -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. vim.api.nvim_command('normal! a') + elseif phase < 2 and mode == 'R' then + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) else vim.api.nvim_put(lines, 'c', false, true) end diff --git a/src/nvim/option.c b/src/nvim/option.c index d7675f48d7..84ace55d91 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -7453,6 +7453,10 @@ dict_T *get_winbuf_options(const int bufopt) /// global value when appropriate. long get_scrolloff_value(void) { + // Disallow scrolloff in terminal-mode. #11915 + if (State & TERM_FOCUS) { + return 0; + } return curwin->w_p_so < 0 ? p_so : curwin->w_p_so; } @@ -7462,4 +7466,3 @@ long get_sidescrolloff_value(void) { return curwin->w_p_siso < 0 ? p_siso : curwin->w_p_siso; } - diff --git a/src/nvim/tag.c b/src/nvim/tag.c index ff07a00952..81d1ef4c9f 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1182,7 +1182,7 @@ static int find_tagfunc_tags( if (result == FAIL) { return FAIL; } - if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_number == VV_NULL) { + if (rettv.v_type == VAR_SPECIAL && rettv.vval.v_special == kSpecialVarNull) { tv_clear(&rettv); return NOTDONE; } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 560a345333..a096b77ac6 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -343,12 +343,16 @@ void terminal_enter(void) RedrawingDisabled = false; // Disable these options in terminal-mode. They are nonsense because cursor is - // placed at end of buffer to "follow" output. + // placed at end of buffer to "follow" output. #11072 win_T *save_curwin = curwin; int save_w_p_cul = curwin->w_p_cul; int save_w_p_cuc = curwin->w_p_cuc; + long save_w_p_so = curwin->w_p_so; + long save_w_p_siso = curwin->w_p_siso; curwin->w_p_cul = false; curwin->w_p_cuc = false; + curwin->w_p_so = 0; + curwin->w_p_siso = 0; adjust_topline(s->term, buf, 0); // scroll to end // erase the unfocused cursor @@ -370,6 +374,8 @@ void terminal_enter(void) if (save_curwin == curwin) { // save_curwin may be invalid (window closed)! curwin->w_p_cul = save_w_p_cul; curwin->w_p_cuc = save_w_p_cuc; + curwin->w_p_so = save_w_p_so; + curwin->w_p_siso = save_w_p_siso; } // draw the unfocused cursor diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index bd5cb6ad19..51689db9c4 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1307,3 +1307,33 @@ func Test_bufadd_bufload() bwipe otherName call assert_equal(0, bufexists('someName')) endfunc + +func Test_readdir() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/dir') + + " All results + let files = readdir('Xdir') + call assert_equal(['bar.txt', 'dir', 'foo.txt'], sort(files)) + + " Only results containing "f" + let files = readdir('Xdir', { x -> stridx(x, 'f') !=- 1 }) + call assert_equal(['foo.txt'], sort(files)) + + " Only .txt files + let files = readdir('Xdir', { x -> x =~ '.txt$' }) + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Only .txt files with string + let files = readdir('Xdir', 'v:val =~ ".txt$"') + call assert_equal(['bar.txt', 'foo.txt'], sort(files)) + + " Limit to 1 result. + let l = [] + let files = readdir('Xdir', {x -> len(add(l, x)) == 2 ? -1 : 1}) + call assert_equal(1, len(files)) + + call delete('Xdir', 'rf') +endfunc diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index fb3755cb8e..2e9d0f57ac 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -571,6 +571,28 @@ describe('API', function() eq({0,7,1,0}, funcs.getpos('.')) eq(false, nvim('get_option', 'paste')) end) + it('Replace-mode', function() + -- Within single line + nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false) + command('normal l') + command('startreplace') + nvim('paste', '123456', true, -1) + expect([[ + a123456d + eeffgghh + iijjkkll]]) + command('%delete _') + -- Across lines + nvim('put', {'aabbccdd', 'eeffgghh', 'iijjkkll'}, "c", true, false) + command('normal l') + command('startreplace') + nvim('paste', '123\n456', true, -1) + expect([[ + a123 + 456d + eeffgghh + iijjkkll]]) + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 128c7c6137..a3b8e685e1 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -113,4 +113,15 @@ describe('URI methods', function() end) end) end) + + describe('uri to bufnr', function() + it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris', function() + local uri = 'jdt://contents/java.base/java.util/List.class?=sql/%5C/home%5C/user%5C/.jabba%5C/jdk%5C/openjdk%5C@1.14.0%5C/lib%5C/jrt-fs.jar%60java.base=/javadoc_location=/https:%5C/%5C/docs.oracle.com%5C/en%5C/java%5C/javase%5C/14%5C/docs%5C/api%5C/=/%3Cjava.util(List.class' + local test_case = string.format([[ + local uri = '%s' + return vim.uri_from_bufnr(vim.uri_to_bufnr(uri)) + ]], uri) + eq(uri, exec_lua(test_case)) + end) + end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1a0063b43e..79f6ef9dd2 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -770,13 +770,14 @@ describe('LSP', function() exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]])) end) - describe('apply_edits', function() + describe('apply_text_edits', function() before_each(function() insert(dedent([[ First line of text Second line of text Third line of text - Fourth line of text]])) + Fourth line of text + å å ɧ 汉语 ↥ 🤦 🦄]])) end) it('applies apply simple edits', function() local edits = { @@ -790,6 +791,7 @@ describe('LSP', function() '2econd line of text'; '3ird line of text'; 'Fourth line of text'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; }, buf_lines(1)) end) it('applies complex edits', function() @@ -813,8 +815,104 @@ describe('LSP', function() 'The next line of text'; 'another line of text'; 'before this!'; + 'å å ɧ 汉语 ↥ 🤦 🦄'; }, buf_lines(1)) end) + pending('applies non-ASCII characters edits', function() + -- FIXME: We don't handle non-ASCII characters well in UTF-16 + local edits = { + make_edit(4, 0, 4, 14, {"a a h"}); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'a a h'; + }, buf_lines(1)) + end) + end) + + describe('apply_text_document_edit', function() + local target_bufnr + before_each(function() + target_bufnr = exec_lua [[ + local bufnr = vim.fn.bufadd("fake/uri") + local lines = {"1st line of text", "2nd line of text"} + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + return bufnr + ]] + end) + it('correctly goes ahead with the edit if all is normal', function() + local text_document_edit = { + edits = { + make_edit(0, 0, 0, 3, "First") + }, + textDocument = { + uri = "file://fake/uri"; + version = 5 + } + } + exec_lua([[ + local args = {...} + local target_bufnr = args[2] + vim.lsp.util.buf_versions[target_bufnr] = 4 + vim.lsp.util.apply_text_document_edit(...) + ]], text_document_edit, target_bufnr) + eq({ + 'First line of text'; + '2nd line of text'; + }, buf_lines(target_bufnr)) + end) + it('correctly goes ahead with the edit if the version is vim.NIL', function() + -- we get vim.NIL when we decode json null value. + local json = exec_lua[[ + return vim.fn.json_decode("{ \"a\": 1, \"b\": null }") + ]] + eq(json.b, exec_lua("return vim.NIL")) + + local text_document_edit = { + edits = { + make_edit(0, 0, 0, 3, "First") + }, + textDocument = { + uri = "file://fake/uri"; + version = exec_lua("return vim.NIL") + } + } + exec_lua([[ + local args = {...} + local target_bufnr = args[2] + vim.lsp.util.buf_versions[target_bufnr] = vim.NIL + vim.lsp.util.apply_text_document_edit(...) + ]], text_document_edit, target_bufnr) + eq({ + 'First line of text'; + '2nd line of text'; + }, buf_lines(target_bufnr)) + end) + it('skips the edit if the version of the edit is behind the local buffer ', function() + local text_document_edit = { + edits = { + make_edit(0, 0, 0, 3, "First") + }, + textDocument = { + uri = "file://fake/uri"; + version = 1 + } + } + exec_lua([[ + local args = {...} + local target_bufnr = args[2] + vim.lsp.util.buf_versions[target_bufnr] = 2 + vim.lsp.util.apply_text_document_edit(...) + ]], text_document_edit, target_bufnr) + eq({ + '1st line of text'; + '2nd line of text'; + }, buf_lines(target_bufnr)) + end) end) describe('completion_list_to_complete_items', function() diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index d40baca871..f79aceaddf 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -17,6 +17,18 @@ describe(':terminal buffer', function() screen = thelpers.screen_setup() end) + it('terminal-mode forces various options', function() + feed([[<C-\><C-N>]]) + command('setlocal cursorline cursorcolumn scrolloff=4 sidescrolloff=7') + eq({ 1, 1, 4, 7 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + eq('n', eval('mode()')) + + -- Enter terminal-mode ("insert" mode in :terminal). + feed('i') + eq('t', eval('mode()')) + eq({ 0, 0, 0, 0 }, eval('[&l:cursorline, &l:cursorcolumn, &l:scrolloff, &l:sidescrolloff]')) + end) + describe('when a new file is edited', function() before_each(function() feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>') |