diff options
-rw-r--r-- | CONTRIBUTING.md | 253 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | contrib/neovim_gdb/neovim_gdb.vim | 344 | ||||
-rw-r--r-- | runtime/autoload/vimexpect.vim | 154 | ||||
-rw-r--r-- | src/nvim/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/nvim/eval.c | 15 | ||||
-rw-r--r-- | src/nvim/eval_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/ex_cmds.c | 92 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 51 | ||||
-rw-r--r-- | src/nvim/option.c | 3 | ||||
-rw-r--r-- | src/nvim/os/env.c | 17 | ||||
-rw-r--r-- | src/nvim/os/wstream.c | 6 | ||||
-rw-r--r-- | src/nvim/terminal.c | 2 | ||||
-rw-r--r-- | src/nvim/version.c | 4 | ||||
-rw-r--r-- | test/functional/job/job_spec.lua | 25 | ||||
-rw-r--r-- | test/functional/ui/screen.lua | 39 | ||||
-rw-r--r-- | test/functional/ui/screen_basic_spec.lua | 3 |
17 files changed, 826 insertions, 191 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1b9d50143..2cb202b201 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,104 +3,191 @@ ## Getting started - Help us review [open pull requests](https://github.com/neovim/neovim/pulls)! -- Look for [**entry-level**][entry] issues to work on. - - [**documentation**](https://github.com/neovim/neovim/labels/documentation) - improvements are also very helpful. +- Look for [entry-level issues][entry-level] to work on. + - [Documentation](https://github.com/neovim/neovim/labels/documentation) + improvements are also much appreciated. - Look at [Waffle][waffle] to see who is working on what issues. -- Refer to the [the wiki][wiki] for detailed guidance. - -## Issues - -- Search existing issues before raising a new one. -- Include as much detail as possible. In particular, we need to know which - OS you're using. - -## Pull requests - +- If needed, refer to [the wiki][wiki-contributing] for guidance. + +## Reporting problems + +Before reporting an issue: + +- Verify that it hasn't already been reported. +- If not already running the latest version of Neovim, update to it to see if + your problem persists. +- If you're experiencing compile or runtime warnings/failures, try searching for + the error message(s) you received (if any) on [Neovim's issue tracker][github-issues]. + - For installation issues, see [Installing Neovim#troubleshooting][wiki-install-troubleshooting]. + - For build issues, see [Building Neovim#troubleshooting][wiki-building-troubleshooting]. + - For runtime issues: try to reproduce them by running `nvim` with the + smallest possible `nvimrc` (or none at all via `nvim -u NONE`), to rule + out bugs in plugins you're using. If you're using a plugin manager, + comment out your plugins, then add them back in one by one. + +Include as much detail as possible; we generally need to know: + +- What operating system you're using. +- Which version of Neovim you're using. To get this, run `nvim --version` from + a shell, or run `:version` from inside `nvim`. +- Whether the bug is present in Vim (not Neovim), and if so which version of + Vim. It's fine to report Vim bugs on the Neovim bug tracker, but it saves + everyone time if we know from the start that the bug is not a regression + caused by Neovim. +- This isn't required, but what commit introduced the issue for you. You can + use [`git bisect`][git-bisect] for this. + +## Submitting contributions + +- If you're a first-time contributor, please sign the [Neovim Contributor + License Agreement (CLA)][cla] before submitting your pull request. - Make it clear in the issue tracker what you are working on. -- Be descriptive in your PR message: what is it for, why is it needed, etc. -- Don't make cosmetic changes to unrelated files in the same PR. -- If you're a first-time contributor, please sign the - [Neovim Contributor License Agreement (CLA)][cla] before submitting your PR. +- Be descriptive in your pull request description: what is it for, why is it + needed, etc. +- Do ***not*** make cosmetic changes to unrelated files in the same pull + request. This creates noise, making reviews harder to do. If your text + editor strips all trailing whitespace in a file when you edit it, disable + it. + +### Tagging in the issue tracker + +When submitting pull requests (commonly referred to as "PRs"), include one of +the following tags prepended to the title: + +- `[WIP]` - Work In Progress: the PR will change, so while there is no + immediate need for review, the submitter still might appreciate it. +- `[RFC]` - Request For Comment: the PR needs reviewing and/or comments. +- `[RDY]` - Ready: the PR has been reviewed by at least one other person and + has no outstanding issues. + +Assuming the above criteria has been met, feel free to change your PR's tag +yourself, as opposed to waiting for a contributor to do it for you. + +### Branching & history + +- Do ***not*** work on your PR on the master branch, [use a feature branch + instead][git-feature-branch]. +- [Rebase your feature branch onto][git-rebasing] (upstream) master before + opening the PR. +- Keep up to date with changes in (upstream) master so your PR is easy to + merge. +- [Try to actively tidy your history][git-history-rewriting]: combine related + commits with interactive rebasing, separate monolithic commits, etc. If your + PR is still `[WIP]`, feel free to force-push to your feature branch to tidy + your history. + +### For code pull requests -#### Tagging in the issue tracker +#### Testing -When submitting pull requests, include one of the following tokens in the title: +We are unlikely to merge your PR if the Travis build fails: -* `[WIP]` - Work In Progress. The pull request will change, and there is no need - to review it yet. -* `[RFC]` - Request For Comment. The request needs reviewing and/or comments. -* `[RDY]` - The request is ready to be merged. The request must have been - reviewed by at least one person and have no outstanding issues. -* Default label is assumed to be `[WIP]` if there's no indication otherwise. +- Travis builds are compiled with the [`-Werror`][gcc-warnings] flag, so if + your PR introduces any compiler warnings then the Travis build will fail. +- If any tests fail, the Travis build will fail. See [Building + Neovim#testing][wiki-building-testing] for information on running tests + locally. Tests passing locally doesn't guarantee they'll pass in the Travis + build, as different compilers and platforms will be used. +- Travis runs [Valgrind][valgrind] for the GCC/Linux build, but you may also + do so locally by running the following from a shell: `VALGRIND=1 make test` -#### Branching & history +#### Coding style -- Use a feature branch, not master. -- Rebase your feature branch onto (upstream) master before raising the PR. -- Keep up to date with changes in (upstream) master so your PR is easy to merge. -- Try to actively tidy your history: combine related commits with interactive - rebasing etc. If your PR is still `[WIP]` don't be afraid to force-push to - your feature branch to tidy your history. +We have a [style guide][style-guide] that all new code should follow. +However, large portions of the existing Vim codebase violate it to some +degree, and fixing them would increase merge conflicts and add noise to `git +blame`. + +Weigh those costs when making cosmetic changes. In general, avoid pull +requests dominated by style changes, but feel free to fix up lines that you +happen to be modifying anyway. Fix anything that looks outright +[barbarous](http://www.orwell.ru/library/essays/politics/english/e_polit), but +otherwise prefer to leave things as they are. + +For new code, run `make lint` (which runs [clint.py][clint]) to detect style +errors. Make sure that the file(s) you intend to be linted are not in +`clint-ignored-files.txt`. It's not perfect, so some warnings may be false +positives/negatives. To have `clint.py` ignore certain cases, put `// NOLINT` +at the end of the line. + +We also provide a configuration file for [`clang-format`][clang-format], which +can be used to format code according to the style guidelines. Be aware that +this formatting method might need user supervision. To have `clang-format` +ignore certain line ranges, use the following special comments: + +```c +int formatted_code; +// clang-format off + void unformatted_code ; +// clang-format on + void formatted_code_again; +``` + +### Commit guidelines + +The purpose of these guidelines is to *make reviews easier* and make the +[VCS][vcs] logs more valuable. + +- Try to keep the first line under 72 characters. +- If necessary, include further description after a blank line. + - Don't make the description too verbose by including obvious things, but + don't spare clarifications for anything that may be not so obvious. + Some commit messages are pages long, and that's fine if there's no + better place for those comments to live. + - **Recommended:** Prefix logically-related commits with a consistent + identifier in each commit message. For already used identifiers, see the + commit history for the respective file(s) you're editing. + [For example](https://github.com/neovim/neovim/commits?author=elmart), + the following commits are related by task (*Introduce nvim namespace*) and + sub-task (*Contrib YCM*). + <br/> `Introduce nvim namespace: Contrib YCM: Fix style issues` + <br/> `Introduce nvim namespace: Contrib YCM: Fix build dir calculation` + - Sub-tasks can be *activity-oriented* (doing different things on the same area) + or *scope-oriented* (doing the same thing in different areas). + - Granularity helps, but it's conceptual size that matters, not extent size. +- Use the [imperative voice][imperative]: "Fix bug" rather than "Fixed bug" or "Fixes bug." -### For code PRs +### Reviewing pull requests -#### Testing +Using a checklist during reviews is highly recommended, so we [provide one at +the wiki][wiki-review-checklist]. If you think it could be improved, feel free +to edit it. -- We are unlikely to merge your PR if the Travis build fails. -- The Travis build does not currently run the tests under valgrind, but you - are encouraged to do so locally. +Reviewing can be done on GitHub, but you may find it easier to do locally. +Using [`hub`][hub], you can do the following to create a new branch with the +contents of a pull request, such as [#1820][github-pr-1820]: -#### Coding style + hub checkout https://github.com/neovim/neovim/pull/1820 -We have a [style guide][style] that all new code should follow. However, vast -swathes of the existing vim codebase violate it to some degree, and fixing -them would increase merge conflicts and add noise to `git blame`. Please weigh -those costs when making cosmetic changes. As a rule of thumb, avoid pull -requests dominated by style changes. Feel free to fix up lines that you happen -to be modifying anyway, as long as they look consistent with their -surroundings. Fix anything that looks outright -[barbarous](http://www.orwell.ru/library/essays/politics/english/e_polit) -- -especially if you can't find any editor settings that make it look ok -- but -otherwise err on the side of leaving things as they are. - -For new code, please run [`clint.py`][clint] to detect style errors. It is not -perfect and may have false positives and negatives. To have `clint.py` ignore -certain special cases, put `// NOLINT` at the end of the line. - -We also provide a configuration file for [`clang-format` and -`git-clang-format`][clang-format], which can be used to format code according -to the style guidelines. Be aware this formatting method might need user -supervision. - -#### Commit guidelines - -The purpose of these guidelines is to *make reviews easier* and make the VCS logs more valuable. - -- Try to keep the first line under 70 characters. -- Include further description, if necessary, after a blank line. - - Don't make it too verbose by including obvious things. - - But don't spare clarifications for anything that could be not so obvious. - Some commit messages are pages long, and that's fine if there's no better - place for those comments to live. - - **Recommended:** Prefix logically-related commits with a consistent - identifier at the beginning of each commit message. - [For example](https://github.com/neovim/neovim/commits?author=elmart), - the following commits are related by task (*Introduce vim namespace*) and - scope (*Contrib YCM*). - <br/> `Introduce vim namespace: Contrib YCM: Fix style issues.` - <br/> `Introduce vim namespace: Contrib YCM: Fix build dir calculation` - - Subtasks can be *activity-oriented* (doing different things on the same area) - or *scope-oriented* (doing the same thing on different areas). - - Granularity helps, but it's conceptual size that matters, not extent size. -- Use the imperative voice: "Fix bug" rather than "Fixed bug" or "Fixes bug." +Use [`git log -p master..FETCH_HEAD`][git-history-filtering] to list all +commits in the feature branch which aren't in the `master` branch; `-p` +shows each commit's diff. To show the whole surrounding function of a change +as context, use the `-W` argument as well. +You may find it easier to instead use an interactive program for code reviews, +such as [`tig`][tig]. [cla]: https://docs.google.com/forms/d/1u54bpbwzneDIRltFx1TGi2evKxY3w0cOV3vlpj8DPbg/viewform -[clint]: clint.py [clang-format]: http://clang.llvm.org/docs/ClangFormat.html -[entry]: https://github.com/neovim/neovim/issues?labels=entry-level&state=open +[clint]: clint.py +[entry-level]: https://github.com/neovim/neovim/issues?labels=entry-level&state=open +[gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html +[git-bisect]: http://git-scm.com/book/tr/v2/Git-Tools-Debugging-with-Git +[git-feature-branch]: https://www.atlassian.com/git/tutorials/comparing-workflows +[git-history-filtering]: https://www.atlassian.com/git/tutorials/git-log/filtering-the-commit-history +[git-history-rewriting]: http://git-scm.com/book/en/v2/Git-Tools-Rewriting-History +[git-rebasing]: http://git-scm.com/book/en/v2/Git-Branching-Rebasing +[github-issues]: https://github.com/neovim/neovim/issues +[github-pr-1820]: https://github.com/neovim/neovim/pull/1820 +[hub]: https://hub.github.com/ [imperative]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html -[style]: http://neovim.org/develop/style-guide.xml +[style-guide]: http://neovim.org/develop/style-guide.xml +[tig]: https://github.com/jonas/tig +[valgrind]: http://valgrind.org/ +[vcs]: https://en.wikipedia.org/wiki/Revision_control [waffle]: https://waffle.io/neovim/neovim -[wiki]: https://github.com/neovim/neovim/wiki/Contributing +[wiki-building-testing]: https://github.com/neovim/neovim/wiki/Building-Neovim#testing +[wiki-building-troubleshooting]: https://github.com/neovim/neovim/wiki/Building-Neovim#troubleshooting +[wiki-contributing]: https://github.com/neovim/neovim/wiki/Contributing +[wiki-install-troubleshooting]: https://github.com/neovim/neovim/wiki/Installing-Neovim#troubleshooting +[wiki-review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist @@ -44,7 +44,7 @@ For lots more details, see ### How do I get it? There is a formula for OSX/homebrew, a PKGBUILD for Arch Linux, RPM, deb, and -more. See [the wiki](https://github.com/neovim/neovim/wiki/Installing)! +more. See [the wiki](https://github.com/neovim/neovim/wiki/Installing-Neovim)! ### Community diff --git a/contrib/neovim_gdb/neovim_gdb.vim b/contrib/neovim_gdb/neovim_gdb.vim new file mode 100644 index 0000000000..d61e7bc0cc --- /dev/null +++ b/contrib/neovim_gdb/neovim_gdb.vim @@ -0,0 +1,344 @@ +sign define GdbBreakpoint text=● +sign define GdbCurrentLine text=⇒ + + +let s:gdb_port = 7778 +let s:run_gdb = "gdb -q -f build/bin/nvim" +let s:breakpoints = {} +let s:max_breakpoint_sign_id = 0 + + +let s:GdbServer = {} + + +function s:GdbServer.new(gdb) + let this = copy(self) + let this._gdb = a:gdb + return this +endfunction + + +function s:GdbServer.on_exit() + let self._gdb._server_exited = 1 +endfunction + + +let s:GdbPaused = vimexpect#State([ + \ ['Continuing.', 'continue'], + \ ['\v[\o32]{2}([^:]+):(\d+):\d+', 'jump'], + \ ['Remote communication error. Target disconnected.:', 'retry'], + \ ]) + + +function s:GdbPaused.continue(...) + call self._parser.switch(s:GdbRunning) + call self.update_current_line_sign(0) +endfunction + + +function s:GdbPaused.jump(file, line, ...) + if tabpagenr() != self._tab + " Don't jump if we are not in the debugger tab + return + endif + let window = winnr() + exe self._jump_window 'wincmd w' + let self._current_buf = bufnr('%') + let target_buf = bufnr(a:file, 1) + if bufnr('%') != target_buf + exe 'buffer ' target_buf + let self._current_buf = target_buf + endif + exe ':' a:line + let self._current_line = a:line + exe window 'wincmd w' + call self.update_current_line_sign(1) +endfunction + + +function s:GdbPaused.retry(...) + if self._server_exited + return + endif + sleep 1 + call self.attach() + call self.send('continue') +endfunction + + +let s:GdbRunning = vimexpect#State([ + \ ['\v^Breakpoint \d+', 'pause'], + \ ['\v\[Inferior\ +.{-}\ +exited\ +normally', 'disconnected'], + \ ['(gdb)', 'pause'], + \ ]) + + +function s:GdbRunning.pause(...) + call self._parser.switch(s:GdbPaused) + if !self._initialized + call self.send('set confirm off') + call self.send('set pagination off') + if !empty(self._server_addr) + call self.send('set remotetimeout 50') + call self.attach() + call s:RefreshBreakpoints() + call self.send('c') + endif + let self._initialized = 1 + endif +endfunction + + +function s:GdbRunning.disconnected(...) + if !self._server_exited && self._reconnect + " Refresh to force a delete of all watchpoints + call s:RefreshBreakpoints() + sleep 1 + call self.attach() + call self.send('continue') + endif +endfunction + + +let s:Gdb = {} + + +function s:Gdb.kill() + tunmap <f8> + tunmap <f10> + tunmap <f11> + tunmap <f12> + call self.update_current_line_sign(0) + exe 'bd! '.self._client_buf + if self._server_buf != -1 + exe 'bd! '.self._server_buf + endif + exe 'tabnext '.self._tab + tabclose + unlet g:gdb +endfunction + + +function! s:Gdb.send(data) + call jobsend(self._client_id, a:data."\<cr>") +endfunction + + +function! s:Gdb.attach() + call self.send(printf('target remote %s', self._server_addr)) +endfunction + + +function! s:Gdb.update_current_line_sign(add) + " to avoid flicker when removing/adding the sign column(due to the change in + " line width), we switch ids for the line sign and only remove the old line + " sign after marking the new one + let old_line_sign_id = get(self, '_line_sign_id', 4999) + let self._line_sign_id = old_line_sign_id == 4999 ? 4998 : 4999 + if a:add && self._current_line != -1 && self._current_buf != -1 + exe 'sign place '.self._line_sign_id.' name=GdbCurrentLine line=' + \.self._current_line.' buffer='.self._current_buf + endif + exe 'sign unplace '.old_line_sign_id +endfunction + + +function! s:Spawn(server_cmd, client_cmd, server_addr, reconnect) + if exists('g:gdb') + throw 'Gdb already running' + endif + let gdb = vimexpect#Parser(s:GdbRunning, copy(s:Gdb)) + " gdbserver port + let gdb._server_addr = a:server_addr + let gdb._reconnect = a:reconnect + let gdb._initialized = 0 + " window number that will be displaying the current file + let gdb._jump_window = 1 + let gdb._current_buf = -1 + let gdb._current_line = -1 + let gdb._has_breakpoints = 0 + let gdb._server_exited = 0 + " Create new tab for the debugging view + tabnew + let gdb._tab = tabpagenr() + " create horizontal split to display the current file and maybe gdbserver + sp + let gdb._server_buf = -1 + if type(a:server_cmd) == type('') + " spawn gdbserver in a vertical split + let server = s:GdbServer.new(gdb) + vsp | enew | let gdb._server_id = termopen(a:server_cmd, server) + let gdb._jump_window = 2 + let gdb._server_buf = bufnr('%') + endif + " go to the bottom window and spawn gdb client + wincmd j + enew | let gdb._client_id = termopen(a:client_cmd, gdb) + let gdb._client_buf = bufnr('%') + tnoremap <silent> <f8> <c-\><c-n>:GdbContinue<cr>i + tnoremap <silent> <f10> <c-\><c-n>:GdbNext<cr>i + tnoremap <silent> <f11> <c-\><c-n>:GdbStep<cr>i + tnoremap <silent> <f12> <c-\><c-n>:GdbFinish<cr>i + " go to the window that displays the current file + exe gdb._jump_window 'wincmd w' + let g:gdb = gdb +endfunction + + +function! s:Test(bang, filter) + let cmd = "GDB=1 make test" + if a:bang == '!' + let server_addr = '| vgdb' + let cmd = printf('VALGRIND=1 %s', cmd) + else + let server_addr = printf('localhost:%d', s:gdb_port) + let cmd = printf('GDBSERVER_PORT=%d %s', s:gdb_port, cmd) + endif + if a:filter != '' + let cmd = printf('TEST_SCREEN_TIMEOUT=1000000 TEST_FILTER="%s" %s', a:filter, cmd) + endif + call s:Spawn(cmd, s:run_gdb, server_addr, 1) +endfunction + + +function! s:ToggleBreak() + let file_name = bufname('%') + let file_breakpoints = get(s:breakpoints, file_name, {}) + let linenr = line('.') + if has_key(file_breakpoints, linenr) + call remove(file_breakpoints, linenr) + else + let file_breakpoints[linenr] = 1 + endif + let s:breakpoints[file_name] = file_breakpoints + call s:RefreshBreakpointSigns() + call s:RefreshBreakpoints() +endfunction + + +function! s:ClearBreak() + let s:breakpoints = {} + call s:RefreshBreakpointSigns() + call s:RefreshBreakpoints() +endfunction + + +function! s:RefreshBreakpointSigns() + let buf = bufnr('%') + let i = 5000 + while i <= s:max_breakpoint_sign_id + exe 'sign unplace '.i + let i += 1 + endwhile + let s:max_breakpoint_sign_id = 0 + let id = 5000 + for linenr in keys(get(s:breakpoints, bufname('%'), {})) + exe 'sign place '.id.' name=GdbBreakpoint line='.linenr.' buffer='.buf + let s:max_breakpoint_sign_id = id + let id += 1 + endfor +endfunction + + +function! s:RefreshBreakpoints() + if !exists('g:gdb') + return + endif + if g:gdb._parser.state() == s:GdbRunning + " pause first + call jobsend(g:gdb._client_id, "\<c-c>") + endif + if g:gdb._has_breakpoints + call g:gdb.send('delete') + endif + let g:gdb._has_breakpoints = 0 + for [file, breakpoints] in items(s:breakpoints) + for linenr in keys(breakpoints) + let g:gdb._has_breakpoints = 1 + call g:gdb.send('break '.file.':'.linenr) + endfor + endfor +endfunction + + +function! s:GetExpression(...) range + let [lnum1, col1] = getpos("'<")[1:2] + let [lnum2, col2] = getpos("'>")[1:2] + let lines = getline(lnum1, lnum2) + let lines[-1] = lines[-1][:col2 - 1] + let lines[0] = lines[0][col1 - 1:] + return join(lines, "\n") +endfunction + + +function! s:Send(data) + if !exists('g:gdb') + throw 'Gdb is not running' + endif + call g:gdb.send(a:data) +endfunction + + +function! s:Eval(expr) + call s:Send(printf('print %s', a:expr)) +endfunction + + +function! s:Watch(expr) + let expr = a:expr + if expr[0] != '&' + let expr = '&' . expr + endif + + call s:Eval(expr) + call s:Send('watch *$') +endfunction + + +function! s:Interrupt() + if !exists('g:gdb') + throw 'Gdb is not running' + endif + call jobsend(g:gdb._client_id, "\<c-c>info line\<cr>") +endfunction + + +function! s:Kill() + if !exists('g:gdb') + throw 'Gdb is not running' + endif + call g:gdb.kill() +endfunction + + +command! GdbDebugNvim call s:Spawn(printf('make && gdbserver localhost:%d build/bin/nvim', s:gdb_port), s:run_gdb, printf('localhost:%d', s:gdb_port), 0) +command! -nargs=1 GdbDebugServer call s:Spawn(0, s:run_gdb, 'localhost:'.<q-args>, 0) +command! -bang -nargs=? GdbDebugTest call s:Test(<q-bang>, <q-args>) +command! -nargs=1 -complete=file GdbInspectCore call s:Spawn(0, printf('gdb -q -f -c %s build/bin/nvim', <q-args>), 0, 0) +command! GdbDebugStop call s:Kill() +command! GdbToggleBreakpoint call s:ToggleBreak() +command! GdbClearBreakpoints call s:ClearBreak() +command! GdbContinue call s:Send("c") +command! GdbNext call s:Send("n") +command! GdbStep call s:Send("s") +command! GdbFinish call s:Send("finish") +command! GdbFrameUp call s:Send("up") +command! GdbFrameDown call s:Send("down") +command! GdbInterrupt call s:Interrupt() +command! GdbEvalWord call s:Eval(expand('<cword>')) +command! -range GdbEvalRange call s:Eval(s:GetExpression(<f-args>)) +command! GdbWatchWord call s:Watch(expand('<cword>') +command! -range GdbWatchRange call s:Watch(s:GetExpression(<f-args>)) + + +nnoremap <silent> <f8> :GdbContinue<cr> +nnoremap <silent> <f10> :GdbNext<cr> +nnoremap <silent> <f11> :GdbStep<cr> +nnoremap <silent> <f12> :GdbFinish<cr> +nnoremap <silent> <c-b> :GdbToggleBreakpoint<cr> +nnoremap <silent> <m-pageup> :GdbFrameUp<cr> +nnoremap <silent> <m-pagedown> :GdbFrameDown<cr> +nnoremap <silent> <f9> :GdbEvalWord<cr> +vnoremap <silent> <f9> :GdbEvalRange<cr> +nnoremap <silent> <m-f9> :GdbWatchWord<cr> +vnoremap <silent> <m-f9> :GdbWatchRange<cr> diff --git a/runtime/autoload/vimexpect.vim b/runtime/autoload/vimexpect.vim new file mode 100644 index 0000000000..16e7d30d6c --- /dev/null +++ b/runtime/autoload/vimexpect.vim @@ -0,0 +1,154 @@ +" vimexpect.vim is a small object-oriented library that simplifies the task of +" scripting communication with jobs or any interactive program. The name +" `expect` comes from the famous tcl extension that has the same purpose. +" +" This library is built upon two simple concepts: Parsers and States. +" +" A State represents a program state and associates a set of regular +" expressions(to parse program output) with method names(to deal with parsed +" output). States are created with the vimexpect#State(patterns) function. +" +" A Parser manages data received from the program. It also manages State +" objects by storing them into a stack, where the top of the stack is the +" current State. Parsers are created with the vimexpect#Parser(initial_state, +" target) function +" +" The State methods are defined by the user, and are always called with `self` +" set as the Parser target. Advanced control flow is achieved by changing the +" current state with the `push`/`pop`/`switch` parser methods. +" +" An example of this library in action can be found in Neovim source +" code(contrib/neovim_gdb subdirectory) + +let s:State = {} + + +" Create a new State instance with a list where each item is a [regexp, name] +" pair. A method named `name` must be defined in the created instance. +function s:State.create(patterns) + let this = copy(self) + let this._patterns = a:patterns + return this +endfunction + + +let s:Parser = {} +let s:Parser.LINE_BUFFER_MAX_LEN = 100 + + +" Create a new Parser instance with the initial state and a target. The target +" is a dictionary that will be the `self` of every State method call associated +" with the parser, and may contain options normally passed to +" `jobstart`(on_stdout/on_stderr will be overriden). Returns the target so it +" can be called directly as the second argument of `jobstart`: +" +" call jobstart(prog_argv, vimexpect#Parser(initial_state, {'pty': 1})) +function s:Parser.create(initial_state, target) + let parser = copy(self) + let parser._line_buffer = [] + let parser._stack = [a:initial_state] + let parser._target = a:target + let parser._target.on_stdout = function('s:JobOutput') + let parser._target.on_stderr = function('s:JobOutput') + let parser._target._parser = parser + return parser._target +endfunction + + +" Push a state to the state stack +function s:Parser.push(state) + call add(self._stack, a:state) +endfunction + + +" Pop a state from the state stack. Fails if there's only one state remaining. +function s:Parser.pop() + if len(self._stack) == 1 + throw 'vimexpect:emptystack:State stack cannot be empty' + endif + return remove(self._stack, -1) +endfunction + + +" Replace the state currently in the top of the stack. +function s:Parser.switch(state) + let old_state = self._stack[-1] + let self._stack[-1] = a:state + return old_state +endfunction + + +" Append a list of lines to the parser line buffer and try to match it the +" current state. This will shift old lines if the buffer crosses its +" limit(defined by the LINE_BUFFER_MAX_LEN field). During normal operation, +" this function is called by the job handler provided by this module, but it +" may be called directly by the user for other purposes(testing for example) +function s:Parser.feed(lines) + if empty(a:lines) + return + endif + let lines = a:lines + let linebuf = self._line_buffer + if lines[0] != "\n" && !empty(linebuf) + " continue the previous line + let linebuf[-1] .= lines[0] + call remove(lines, 0) + endif + " append the newly received lines to the line buffer + let linebuf += lines + " keep trying to match handlers while the line isnt empty + while !empty(linebuf) + let match_idx = self.parse(linebuf) + if match_idx == -1 + break + endif + let linebuf = linebuf[match_idx + 1 : ] + endwhile + " shift excess lines from the buffer + while len(linebuf) > self.LINE_BUFFER_MAX_LEN + call remove(linebuf, 0) + endwhile + let self._line_buffer = linebuf +endfunction + + +" Try to match a list of lines with the current state and call the handler if +" the match succeeds. Return the index in `lines` of the first match. +function s:Parser.parse(lines) + let lines = a:lines + if empty(lines) + return -1 + endif + let state = self.state() + " search for a match using the list of patterns + for [pattern, handler] in state._patterns + let matches = matchlist(lines, pattern) + if empty(matches) + continue + endif + let match_idx = match(lines, pattern) + call call(state[handler], matches[1:], self._target) + return match_idx + endfor +endfunction + + +" Return the current state +function s:Parser.state() + return self._stack[-1] +endfunction + + +" Job handler that simply forwards lines to the parser. +function! s:JobOutput(id, lines) + call self._parser.feed(a:lines) +endfunction + +function vimexpect#Parser(initial_state, target) + return s:Parser.create(a:initial_state, a:target) +endfunction + + +function vimexpect#State(patterns) + return s:State.create(a:patterns) +endfunction diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 92fbc0c8c9..47782e8b6b 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -106,7 +106,9 @@ foreach(gen_include ${gen_includes}) list(APPEND gen_cflags "-I${gen_include}") endforeach() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) -set(gen_cflags "${gen_cflags} ${CMAKE_C_FLAGS_${build_type}} ${CMAKE_C_FLAGS}") +separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) +separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}}) +set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY}) foreach(sfile ${NEOVIM_SOURCES} "${PROJECT_SOURCE_DIR}/src/nvim/regexp_nfa.c") @@ -121,7 +123,6 @@ foreach(sfile ${NEOVIM_SOURCES} set(gf1 "${GENERATED_DIR}/${r}.c.generated.h") set(gf2 "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h") set(gf3 "${GENERATED_DIR}/${r}.i") - separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) add_custom_command( OUTPUT "${gf1}" "${gf2}" COMMAND ${CMAKE_C_COMPILER} ${sfile} -o ${gf3} ${gen_cflags} -E ${C_FLAGS_ARRAY} diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 9d8421ef04..4ab31985b5 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -5560,8 +5560,10 @@ static int free_unref_items(int copyID) bool did_free = false; // Go through the list of dicts and free items without the copyID. + // Don't free dicts that are referenced internally. for (dict_T *dd = first_dict; dd != NULL; ) { - if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + && !dd->internal_refcount) { // Free the Dictionary and ordinary items it contains, but don't // recurse into Lists and Dictionaries, they will be in the list // of dicts or list of lists. */ @@ -5671,6 +5673,7 @@ dict_T *dict_alloc(void) FUNC_ATTR_NONNULL_RET d->dv_scope = 0; d->dv_refcount = 0; d->dv_copyID = 0; + d->internal_refcount = 0; return d; } @@ -10969,6 +10972,9 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv) } } + // poll to ensure any pending callbacks from the last job are invoked + event_poll(0); + for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { Job *job = NULL; if (arg->li_tv.v_type != VAR_NUMBER @@ -20064,6 +20070,7 @@ static inline void common_job_callbacks(dict_T *vopts, ufunc_T **on_stdout, return; } + vopts->internal_refcount++; vopts->dv_refcount++; } @@ -20097,7 +20104,11 @@ static inline void free_term_job_data(TerminalJobData *data) { if (data->on_exit) { user_func_unref(data->on_exit); } - dict_unref(data->self); + + if (data->self) { + data->self->internal_refcount--; + dict_unref(data->self); + } free(data); } diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index d2de830d6c..34a36004d6 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -111,6 +111,8 @@ struct dictvar_S { dict_T *dv_copydict; /* copied dict used by deepcopy() */ dict_T *dv_used_next; /* next dict in used dicts list */ dict_T *dv_used_prev; /* previous dict in used dicts list */ + int internal_refcount; // number of internal references to + // prevent garbage collection }; #endif // NVIM_EVAL_DEFS_H diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index e687eab3c4..c686c5effa 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2790,51 +2790,13 @@ do_ecmd ( oldbuf = (flags & ECMD_OLDBUF); } + buf = curbuf; if ((flags & ECMD_SET_HELP) || keep_help_flag) { - char_u *p; - - curbuf->b_help = true; - set_string_option_direct((char_u *)"buftype", -1, - (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); - - /* - * Always set these options after jumping to a help tag, because the - * user may have an autocommand that gets in the way. - * Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and - * latin1 word characters (for translated help files). - * Only set it when needed, buf_init_chartab() is some work. - */ - p = - (char_u *)"!-~,^*,^|,^\",192-255"; - if (STRCMP(curbuf->b_p_isk, p) != 0) { - set_string_option_direct((char_u *)"isk", -1, p, - OPT_FREE|OPT_LOCAL, 0); - check_buf_options(curbuf); - (void)buf_init_chartab(curbuf, FALSE); - } - - curbuf->b_p_ts = 8; /* 'tabstop' is 8 */ - curwin->w_p_list = FALSE; /* no list mode */ - - curbuf->b_p_ma = FALSE; /* not modifiable */ - curbuf->b_p_bin = FALSE; /* reset 'bin' before reading file */ - curwin->w_p_nu = 0; /* no line numbers */ - curwin->w_p_rnu = 0; /* no relative line numbers */ - RESET_BINDING(curwin); /* no scroll or cursor binding */ - curwin->w_p_arab = FALSE; /* no arabic mode */ - curwin->w_p_rl = FALSE; /* help window is left-to-right */ - curwin->w_p_fen = FALSE; /* No folding in the help window */ - curwin->w_p_diff = FALSE; /* No 'diff' */ - curwin->w_p_spell = FALSE; /* No spell checking */ - - buf = curbuf; - set_buflisted(FALSE); - } else { - buf = curbuf; - /* Don't make a buffer listed if it's a help buffer. Useful when - * using CTRL-O to go back to a help file. */ - if (!curbuf->b_help) - set_buflisted(TRUE); + prepare_help_buffer(); + } else if (!curbuf->b_help) { + // Don't make a buffer listed if it's a help buffer. Useful when using + // CTRL-O to go back to a help file. + set_buflisted(TRUE); } /* If autocommands change buffers under our fingers, forget about @@ -5046,6 +5008,46 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la return OK; } +/// Called when starting to edit a buffer for a help file. +static void prepare_help_buffer(void) +{ + curbuf->b_help = true; + set_string_option_direct((char_u *)"buftype", -1, (char_u *)"help", + OPT_FREE|OPT_LOCAL, 0); + + // Always set these options after jumping to a help tag, because the + // user may have an autocommand that gets in the way. + // Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and + // latin1 word characters (for translated help files). + // Only set it when needed, buf_init_chartab() is some work. + char_u *p = (char_u *)"!-~,^*,^|,^\",192-255"; + if (STRCMP(curbuf->b_p_isk, p) != 0) { + set_string_option_direct((char_u *)"isk", -1, p, OPT_FREE|OPT_LOCAL, 0); + check_buf_options(curbuf); + (void)buf_init_chartab(curbuf, FALSE); + } + + // Don't use the global foldmethod. + set_string_option_direct((char_u *)"fdm", -1, (char_u *)"manual", + OPT_FREE|OPT_LOCAL, 0); + + curbuf->b_p_ts = 8; // 'tabstop' is 8. + curwin->w_p_list = FALSE; // No list mode. + + curbuf->b_p_ma = FALSE; // Not modifiable. + curbuf->b_p_bin = FALSE; // Reset 'bin' before reading file. + curwin->w_p_nu = 0; // No line numbers. + curwin->w_p_rnu = 0; // No relative line numbers. + RESET_BINDING(curwin); // No scroll or cursor binding. + curwin->w_p_arab = FALSE; // No arabic mode. + curwin->w_p_rl = FALSE; // Help window is left-to-right. + curwin->w_p_fen = FALSE; // No folding in the help window. + curwin->w_p_diff = FALSE; // No 'diff'. + curwin->w_p_spell = FALSE; // No spell checking. + + set_buflisted(FALSE); +} + /* * After reading a help file: May cleanup a help buffer when syntax * highlighting is not used. @@ -6316,5 +6318,3 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) } } } - -// vim: tabstop=8 diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index b1f0798528..35549ce042 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -59,6 +59,7 @@ typedef struct { } data; uint64_t next_request_id; kvec_t(ChannelCallFrame *) call_stack; + kvec_t(WBuffer *) delayed_notifications; } Channel; typedef struct { @@ -68,18 +69,10 @@ typedef struct { uint64_t request_id; } RequestEvent; -typedef struct { - Channel *channel; - String method; - Array args; -} DelayedNotification; - #define _noop(x) KMEMPOOL_INIT(RequestEventPool, RequestEvent, _noop) -KLIST_INIT(DelayedNotification, DelayedNotification, _noop) - static kmempool_t(RequestEventPool) *request_event_pool = NULL; -static klist_t(DelayedNotification) *delayed_notifications = NULL; + static uint64_t next_id = 1; static PMap(uint64_t) *channels = NULL; static PMap(cstr_t) *event_strings = NULL; @@ -93,7 +86,6 @@ static msgpack_sbuffer out_buffer; void channel_init(void) { request_event_pool = kmp_init(RequestEventPool); - delayed_notifications = kl_init(DelayedNotification); channels = pmap_new(uint64_t)(); event_strings = pmap_new(cstr_t)(); msgpack_sbuffer_init(&out_buffer); @@ -191,13 +183,10 @@ bool channel_send_event(uint64_t id, char *name, Array args) if (channel) { if (channel->pending_requests) { - DelayedNotification p = { - .channel = channel, - .method = cstr_to_string(name), - .args = args - }; - // Pending request, queue the notification for sending later - *kl_pushp(DelayedNotification, delayed_notifications) = p; + // Pending request, queue the notification for later sending. + String method = cstr_as_string(name); + WBuffer *buffer = serialize_request(id, 0, method, args, &out_buffer, 1); + kv_push(WBuffer *, channel->delayed_notifications, buffer); } else { send_event(channel, name, args); } @@ -248,7 +237,7 @@ Object channel_send_call(uint64_t id, } if (!channel->pending_requests) { - send_delayed_notifications(); + send_delayed_notifications(channel); } decref(channel); @@ -506,6 +495,7 @@ static bool channel_write(Channel *channel, WBuffer *buffer) bool success; if (channel->closed) { + wstream_release_wbuffer(buffer); return false; } @@ -593,7 +583,12 @@ static void broadcast_event(char *name, Array args) kv_size(subscribed)); for (size_t i = 0; i < kv_size(subscribed); i++) { - channel_write(kv_A(subscribed, i), buffer); + Channel *channel = kv_A(subscribed, i); + if (channel->pending_requests) { + kv_push(WBuffer *, channel->delayed_notifications, buffer); + } else { + channel_write(channel, buffer); + } } end: @@ -666,6 +661,7 @@ static void free_channel(Channel *channel) pmap_free(cstr_t)(channel->subscribed_events); kv_destroy(channel->call_stack); + kv_destroy(channel->delayed_notifications); free(channel); } @@ -686,6 +682,7 @@ static Channel *register_channel(void) rv->subscribed_events = pmap_new(cstr_t)(); rv->next_request_id = 1; kv_init(rv->call_stack); + kv_init(rv->delayed_notifications); pmap_put(uint64_t)(channels, rv->id, rv); return rv; } @@ -773,18 +770,14 @@ static WBuffer *serialize_response(uint64_t channel_id, return rv; } -static void send_delayed_notifications(void) +static void send_delayed_notifications(Channel* channel) { - DelayedNotification p; - - while (kl_shift(DelayedNotification, delayed_notifications, &p) == 0) { - if (p.channel) { - send_event(p.channel, p.method.data, p.args); - } else { - broadcast_event(p.method.data, p.args); - } - free(p.method.data); + for (size_t i = 0; i < kv_size(channel->delayed_notifications); i++) { + WBuffer *buffer = kv_A(channel->delayed_notifications, i); + channel_write(channel, buffer); } + + kv_size(channel->delayed_notifications) = 0; } static void incref(Channel *channel) diff --git a/src/nvim/option.c b/src/nvim/option.c index 4f955fee4e..2d016d8350 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -5354,6 +5354,9 @@ set_num_option ( if (p_hi < 0) { errmsg = e_positive; p_hi = 0; + } else if (p_hi > 10000) { + errmsg = e_invarg; + p_hi = 10000; } if (p_re < 0 || p_re > 2) { errmsg = e_invarg; diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 3bea2908d5..30e44341a9 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -243,19 +243,16 @@ void expand_env_esc(char_u *srcp, char_u *dst, int dstlen, bool esc, bool one, // Verify that we have found the end of a UNIX ${VAR} style variable if (src[1] == '{' && *tail != '}') { var = NULL; - } else if (src[1] == '{') { - ++tail; - } -#elif defined(MSWIN) - // Verify that we have found the end of a Windows %VAR% style variable - if (src[0] == '%' && *tail != '%') { - var = NULL; - } else if (src[0] == '%') { - ++tail; - } + } else { + if (src[1] == '{') { + ++tail; + } #endif *var = NUL; var = vim_getenv(dst, &mustfree); +#if defined(UNIX) + } +#endif } else if ( src[1] == NUL /* home directory */ || vim_ispathsep(src[1]) || vim_strchr((char_u *)" ,\t\n", src[1]) != NULL) { diff --git a/src/nvim/os/wstream.c b/src/nvim/os/wstream.c index 90d4ebeec8..13c6c0429f 100644 --- a/src/nvim/os/wstream.c +++ b/src/nvim/os/wstream.c @@ -181,7 +181,7 @@ bool wstream_write(WStream *wstream, WBuffer *buffer) return true; err: - release_wbuffer(buffer); + wstream_release_wbuffer(buffer); return false; } @@ -217,7 +217,7 @@ static void write_cb(uv_write_t *req, int status) data->wstream->curmem -= data->buffer->size; - release_wbuffer(data->buffer); + wstream_release_wbuffer(data->buffer); if (data->wstream->cb) { data->wstream->cb(data->wstream, @@ -239,7 +239,7 @@ static void write_cb(uv_write_t *req, int status) kmp_free(WRequestPool, wrequest_pool, data); } -static void release_wbuffer(WBuffer *buffer) +void wstream_release_wbuffer(WBuffer *buffer) { if (!--buffer->refcount) { if (buffer->cb) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 87b2d8ff99..daba7b943f 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1126,4 +1126,4 @@ static int get_config_int(Terminal *term, char *key) // }}} -// vim: foldmethod=marker foldenable +// vim: foldmethod=marker diff --git a/src/nvim/version.c b/src/nvim/version.c index 45e3a73a69..8cdc06dba5 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -225,7 +225,7 @@ static int included_patches[] = { 518, 517, 516, - //515, + 515, 514, 513, //512 NA @@ -404,7 +404,7 @@ static int included_patches[] = { 339, 338, 337, - //336, + 336, 335, 334, //333 NA diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index c1c559eb34..c517ae4c1b 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -200,19 +200,22 @@ describe('jobs', function() it('will run callbacks while waiting', function() source([[ let g:dict = {'id': 10} - let g:l = [] - function g:dict.on_stdout(id, data) - call add(g:l, a:data[0]) + let g:exits = 0 + function g:dict.on_exit(id, code) + if a:code != 5 + throw 'Error!' + endif + let g:exits += 1 endfunction call jobwait([ - \ jobstart([&sh, '-c', 'sleep 0.010; echo 4'], g:dict), - \ jobstart([&sh, '-c', 'sleep 0.030; echo 5'], g:dict), - \ jobstart([&sh, '-c', 'sleep 0.050; echo 6'], g:dict), - \ jobstart([&sh, '-c', 'sleep 0.070; echo 7'], g:dict) + \ jobstart([&sh, '-c', 'sleep 0.010; exit 5'], g:dict), + \ jobstart([&sh, '-c', 'sleep 0.030; exit 5'], g:dict), + \ jobstart([&sh, '-c', 'sleep 0.050; exit 5'], g:dict), + \ jobstart([&sh, '-c', 'sleep 0.070; exit 5'], g:dict) \ ]) - call rpcnotify(g:channel, 'wait', g:l) + call rpcnotify(g:channel, 'wait', g:exits) ]]) - eq({'notification', 'wait', {{'4', '5', '6', '7'}}}, next_msg()) + eq({'notification', 'wait', {4}}, next_msg()) end) it('will return status codes in the order of passed ids', function() @@ -250,8 +253,8 @@ describe('jobs', function() it('will return -1 if the wait timed out', function() source([[ call rpcnotify(g:channel, 'wait', jobwait([ - \ jobstart([&sh, '-c', 'sleep 0.05; exit 4']), - \ jobstart([&sh, '-c', 'sleep 0.3; exit 5']), + \ jobstart([&sh, '-c', 'exit 4']), + \ jobstart([&sh, '-c', 'sleep 10000; exit 5']), \ ], 100)) ]]) eq({'notification', 'wait', {{4, -1}}}, next_msg()) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 174538df6e..ca6cac1ba3 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -230,20 +230,41 @@ end function Screen:wait(check, timeout) local err, checked = false + local success_seen = false + local failure_after_success = false local function notification_cb(method, args) assert(method == 'redraw') self:_redraw(args) err = check() checked = true if not err then + success_seen = true stop() + elseif success_seen and #args > 0 then + failure_after_success = true + --print(require('inspect')(args)) end + return true end run(nil, notification_cb, nil, timeout or default_screen_timeout) if not checked then err = check() end + + if failure_after_success then + print([[ +Warning: Screen changes have been received after the expected state was seen. +This is probably due to an indeterminism in the test. Try adding +`wait()` (or even a separate `screen:expect(...)`) at a point of possible +indeterminism, typically in between a `feed()` or `execute()` which is non- +synchronous, and a synchronous api call. + ]]) + local tb = debug.traceback() + local index = string.find(tb, '\n%s*%[C]') + print(string.sub(tb,1,index)) + end + if err then assert(false, err) end @@ -456,6 +477,24 @@ end function Screen:snapshot_util(attrs, ignore) -- util to generate screen test pcall(function() self:wait(function() return "error" end, 250) end) + self:print_snapshot(attrs, ignore) +end + +function Screen:redraw_debug(attrs, ignore) + self:print_snapshot(attrs, ignore) + local function notification_cb(method, args) + assert(method == 'redraw') + for _, update in ipairs(args) do + print(require('inspect')(update)) + end + self:_redraw(args) + self:print_snapshot(attrs, ignore) + return true + end + run(nil, notification_cb, nil, 250) +end + +function Screen:print_snapshot(attrs, ignore) if ignore == nil then ignore = self._default_attr_ignore end diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 034e9a05d7..7710918b94 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers') local Screen = require('test.functional.ui.screen') local clear, feed, execute = helpers.clear, helpers.feed, helpers.execute -local insert = helpers.insert +local insert, wait = helpers.insert, helpers.wait describe('Screen', function() local screen @@ -464,6 +464,7 @@ describe('Screen', function() end) it('has minimum width/height values', function() + wait() screen:try_resize(1, 1) screen:expect([[ -- INS^ERT --| |