aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md253
-rw-r--r--README.md2
-rw-r--r--contrib/neovim_gdb/neovim_gdb.vim344
-rw-r--r--runtime/autoload/vimexpect.vim154
-rw-r--r--src/nvim/CMakeLists.txt5
-rw-r--r--src/nvim/eval.c15
-rw-r--r--src/nvim/eval_defs.h2
-rw-r--r--src/nvim/ex_cmds.c92
-rw-r--r--src/nvim/msgpack_rpc/channel.c51
-rw-r--r--src/nvim/option.c3
-rw-r--r--src/nvim/os/env.c17
-rw-r--r--src/nvim/os/wstream.c6
-rw-r--r--src/nvim/terminal.c2
-rw-r--r--src/nvim/version.c4
-rw-r--r--test/functional/job/job_spec.lua25
-rw-r--r--test/functional/ui/screen.lua39
-rw-r--r--test/functional/ui/screen_basic_spec.lua3
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
diff --git a/README.md b/README.md
index 527262aa5a..8eb7ac63b6 100644
--- a/README.md
+++ b/README.md
@@ -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 --|