aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/provider/clipboard.vim9
-rw-r--r--runtime/doc/api-funcs.txt716
-rw-r--r--runtime/syntax/help.vim2
-rw-r--r--runtime/syntax/vim.vim4
-rw-r--r--scripts/gen_api_vimdoc.py514
-rw-r--r--src/nvim/eval.c59
-rw-r--r--src/nvim/globals.h12
-rw-r--r--src/nvim/memory.c10
-rw-r--r--src/nvim/testdir/test_expr.vim11
-rw-r--r--src/nvim/version.c64
-rw-r--r--test/functional/helpers.lua41
-rw-r--r--test/helpers.lua40
-rw-r--r--test/unit/formatc.lua8
-rw-r--r--test/unit/helpers.lua15
-rw-r--r--test/unit/preprocess.lua155
15 files changed, 1489 insertions, 171 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index f63ad5730b..581cec036e 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -22,9 +22,12 @@ function! s:try_cmd(cmd, ...)
let argv = split(a:cmd, " ")
let out = a:0 ? systemlist(argv, a:1, 1) : systemlist(argv, [''], 1)
if v:shell_error
- echohl WarningMsg
- echo "clipboard: error: ".(len(out) ? out[0] : '')
- echohl None
+ if !exists('s:did_error_try_cmd')
+ echohl WarningMsg
+ echomsg "clipboard: error: ".(len(out) ? out[0] : '')
+ echohl None
+ let s:did_error_try_cmd = 1
+ endif
return 0
endif
return out
diff --git a/runtime/doc/api-funcs.txt b/runtime/doc/api-funcs.txt
new file mode 100644
index 0000000000..706941fd12
--- /dev/null
+++ b/runtime/doc/api-funcs.txt
@@ -0,0 +1,716 @@
+*api-funcs.txt* Neovim API Function Reference {Nvim}
+
+Note: This documentation is generated from Neovim's API source code.
+
+Contents:
+
+1. Global Functions |api-global|
+2. Buffer Functions |api-buffer|
+3. Window Functions |api-window|
+4. Tabpage Functions |api-tabpage|
+5. UI Functions |api-ui|
+
+==============================================================================
+1. Global Functions *api-global*
+
+nvim_command({command}) *nvim_command()*
+ Executes an ex-command. On VimL error: Returns the VimL error;
+ v:errmsg is not updated.
+
+ Parameters:~
+ {command} Ex-command string
+
+nvim_feedkeys({keys}, {mode}, {escape_csi}) *nvim_feedkeys()*
+ Passes input keys to Nvim. On VimL error: Does not fail, but
+ updates v:errmsg.
+
+ Parameters:~
+ {keys} to be typed
+ {mode} mapping options
+ {escape_csi} If true, escape K_SPECIAL/CSI bytes in
+ `keys`
+
+nvim_input({keys}) *nvim_input()*
+ Passes keys to Nvim as raw user-input. On VimL error: Does not
+ fail, but updates v:errmsg.
+
+ Unlike `nvim_feedkeys`, this uses a lower-level input buffer
+ and the call is not deferred. This is the most reliable way to
+ emulate real user input.
+
+ Parameters:~
+ {keys} to be typed
+
+ Return:~
+ Number of bytes actually written (can be fewer than
+ requested if the buffer becomes full).
+
+ *nvim_replace_termcodes()*
+nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
+ Replaces any terminal codes with the internal representation
+
+nvim_command_output({str}) *nvim_command_output()*
+ TODO: Documentation
+
+nvim_eval({expr}) *nvim_eval()*
+ Evaluates a VimL expression (:help expression). Dictionaries
+ and Lists are recursively expanded. On VimL error: Returns a
+ generic error; v:errmsg is not updated.
+
+ Parameters:~
+ {expr} VimL expression string
+
+ Return:~
+ Evaluation result or expanded object
+
+nvim_call_function({fname}, {args}) *nvim_call_function()*
+ Calls a VimL function with the given arguments. On VimL error:
+ Returns a generic error; v:errmsg is not updated.
+
+ Parameters:~
+ {fname} Function to call
+ {args} Function arguments packed in an Array
+
+ Return:~
+ Result of the function call
+
+nvim_strwidth({str}) *nvim_strwidth()*
+ Calculates the number of display cells occupied by `text`.
+ <Tab> counts as one cell.
+
+ Parameters:~
+ {text} Some text
+
+ Return:~
+ Number of cells
+
+nvim_list_runtime_paths() *nvim_list_runtime_paths()*
+ Gets the paths contained in 'runtimepath'.
+
+ Return:~
+ List of paths
+
+nvim_set_current_dir({dir}) *nvim_set_current_dir()*
+ Changes the global working directory.
+
+ Parameters:~
+ {dir} Directory path
+
+nvim_get_current_line() *nvim_get_current_line()*
+ Gets the current line
+
+ Parameters:~
+
+ Return:~
+ Current line string
+
+nvim_set_current_line({line}) *nvim_set_current_line()*
+ Sets the current line
+
+ Parameters:~
+ {line} Line contents
+
+nvim_del_current_line() *nvim_del_current_line()*
+ Deletes the current line
+
+ Parameters:~
+
+nvim_get_var({name}) *nvim_get_var()*
+ Gets a global (g:) variable
+
+ Parameters:~
+ {name} Variable name
+
+ Return:~
+ Variable value
+
+nvim_set_var({name}, {value}) *nvim_set_var()*
+ Sets a global (g:) variable
+
+ Parameters:~
+ {name} Variable name
+ {value} Variable value
+
+nvim_del_var({name}) *nvim_del_var()*
+ Removes a global (g:) variable
+
+ Parameters:~
+ {name} Variable name
+
+nvim_get_vvar({name}) *nvim_get_vvar()*
+ Gets a v: variable
+
+ Parameters:~
+ {name} Variable name
+
+ Return:~
+ Variable value
+
+nvim_get_option({name}) *nvim_get_option()*
+ Gets an option value string
+
+ Parameters:~
+ {name} Option name
+
+ Return:~
+ Option value
+
+nvim_set_option({name}, {value}) *nvim_set_option()*
+ Sets an option value
+
+ Parameters:~
+ {name} Option name
+ {value} New option value
+
+nvim_out_write({str}) *nvim_out_write()*
+ Writes a message to vim output buffer
+
+ Parameters:~
+ {str} Message
+
+nvim_err_write({str}) *nvim_err_write()*
+ Writes a message to vim error buffer
+
+ Parameters:~
+ {str} Message
+
+nvim_err_writeln({str}) *nvim_err_writeln()*
+ Writes a message to vim error buffer. Appends a linefeed to
+ ensure all contents are written.
+
+ Parameters:~
+ {str} Message
+
+nvim_list_bufs() *nvim_list_bufs()*
+ Gets the current list of buffer handles
+
+ Return:~
+ List of buffer handles
+
+nvim_get_current_buf() *nvim_get_current_buf()*
+ Gets the current buffer
+
+ Return:~
+ Buffer handle
+
+nvim_set_current_buf({buffer}) *nvim_set_current_buf()*
+ Sets the current buffer
+
+ Parameters:~
+ {id} Buffer handle
+
+nvim_list_wins() *nvim_list_wins()*
+ Gets the current list of window handles
+
+ Return:~
+ List of window handles
+
+nvim_get_current_win() *nvim_get_current_win()*
+ Gets the current window
+
+ Return:~
+ Window handle
+
+nvim_set_current_win({window}) *nvim_set_current_win()*
+ Sets the current window
+
+ Parameters:~
+ {handle} Window handle
+
+nvim_list_tabpages() *nvim_list_tabpages()*
+ Gets the current list of tabpage handles
+
+ Return:~
+ List of tabpage handles
+
+nvim_get_current_tabpage() *nvim_get_current_tabpage()*
+ Gets the current tabpage
+
+ Return:~
+ Tabpage handle
+
+nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()*
+ Sets the current tabpage
+
+ Parameters:~
+ {handle} Tabpage handle
+
+nvim_subscribe({event}) *nvim_subscribe()*
+ Subscribes to event broadcasts
+
+ Parameters:~
+ {event} Event type string
+
+nvim_unsubscribe({event}) *nvim_unsubscribe()*
+ Unsubscribes to event broadcasts
+
+ Parameters:~
+ {event} Event type string
+
+nvim_get_color_by_name({name}) *nvim_get_color_by_name()*
+ TODO: Documentation
+
+nvim_get_color_map() *nvim_get_color_map()*
+ TODO: Documentation
+
+nvim_get_api_info() *nvim_get_api_info()*
+ TODO: Documentation
+
+nvim_call_atomic({calls}) *nvim_call_atomic()*
+ Call many api methods atomically
+
+ This has two main usages: Firstly, to perform several requests
+ from an async context atomically, i.e. without processing
+ requests from other rpc clients or redrawing or allowing user
+ interaction in between. Note that api methods that could fire
+ autocommands or do event processing still might do so. For
+ instance invoking the :sleep command might call timer
+ callbacks. Secondly, it can be used to reduce rpc overhead
+ (roundtrips) when doing many requests in sequence.
+
+ Parameters:~
+ {calls} an array of calls, where each call is described
+ by an array with two elements: the request name,
+ and an array of arguments.
+
+ Return:~
+ an array with two elements. The first is an array of
+ return values. The second is NIL if all calls succeeded.
+ If a call resulted in an error, it is a three-element
+ array with the zero-based index of the call which resulted
+ in an error, the error type and the error message. If an
+ error ocurred, the values from all preceding calls will
+ still be returned.
+
+
+==============================================================================
+2. Buffer Functions *api-buffer*
+
+nvim_buf_line_count({buffer}) *nvim_buf_line_count()*
+ Gets the buffer line count
+
+ Parameters:~
+ {buffer} Buffer handle
+
+ Return:~
+ Line count
+
+ *nvim_buf_get_lines()*
+nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
+ Retrieves a line range from the buffer
+
+ Indexing is zero-based, end-exclusive. Negative indices are
+ interpreted as length+1+index, i e -1 refers to the index past
+ the end. So to get the last element set start=-2 and end=-1.
+
+ Out-of-bounds indices are clamped to the nearest valid value,
+ unless `strict_indexing` is set.
+
+ Parameters:~
+ {buffer} Buffer handle
+ {start} First line index
+ {end} Last line index (exclusive)
+ {strict_indexing} Whether out-of-bounds should be an
+ error.
+
+ Return:~
+ Array of lines
+
+ *nvim_buf_set_lines()*
+nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing},
+ {replacement})
+ Replaces line range on the buffer
+
+ Indexing is zero-based, end-exclusive. Negative indices are
+ interpreted as length+1+index, i e -1 refers to the index past
+ the end. So to change or delete the last element set start=-2
+ and end=-1.
+
+ To insert lines at a given index, set both start and end to
+ the same index. To delete a range of lines, set replacement to
+ an empty array.
+
+ Out-of-bounds indices are clamped to the nearest valid value,
+ unless `strict_indexing` is set.
+
+ Parameters:~
+ {buffer} Buffer handle
+ {start} First line index
+ {end} Last line index (exclusive)
+ {strict_indexing} Whether out-of-bounds should be an
+ error.
+ {replacement} Array of lines to use as replacement
+
+nvim_buf_get_var({buffer}, {name}) *nvim_buf_get_var()*
+ Gets a buffer-scoped (b:) variable.
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Variable name
+
+ Return:~
+ Variable value
+
+nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
+ Sets a buffer-scoped (b:) variable
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Variable name
+ {value} Variable value
+
+nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
+ Removes a buffer-scoped (b:) variable
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Variable name
+
+nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
+ Gets a buffer option value
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Option name
+
+ Return:~
+ Option value
+
+nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()*
+ Sets a buffer option value. Passing 'nil' as value deletes the
+ option (only works if there's a global fallback)
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Option name
+ {value} Option value
+
+nvim_buf_get_number({buffer}) *nvim_buf_get_number()*
+ Gets the buffer number
+
+ Parameters:~
+ {buffer} Buffer handle
+
+ Return:~
+ Buffer number
+
+nvim_buf_get_name({buffer}) *nvim_buf_get_name()*
+ Gets the full file name for the buffer
+
+ Parameters:~
+ {buffer} Buffer handle
+
+ Return:~
+ Buffer name
+
+nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
+ Sets the full file name for a buffer
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Buffer name
+
+nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()*
+ Checks if a buffer is valid
+
+ Parameters:~
+ {buffer} Buffer handle
+
+ Return:~
+ true if the buffer is valid, false otherwise
+
+nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
+ Return a tuple (row,col) representing the position of the
+ named mark
+
+ Parameters:~
+ {buffer} Buffer handle
+ {name} Mark name
+
+ Return:~
+ (row, col) tuple
+
+ *nvim_buf_add_highlight()*
+nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line},
+ {col_start}, {col_end})
+ Adds a highlight to buffer.
+
+ This can be used for plugins which dynamically generate
+ highlights to a buffer (like a semantic highlighter or
+ linter). The function adds a single highlight to a buffer.
+ Unlike matchaddpos() highlights follow changes to line
+ numbering (as lines are inserted/removed above the highlighted
+ line), like signs and marks do.
+
+ "src_id" is useful for batch deletion/updating of a set of
+ highlights. When called with src_id = 0, an unique source id
+ is generated and returned. Succesive calls can pass in it as
+ "src_id" to add new highlights to the same source group. All
+ highlights in the same group can then be cleared with
+ nvim_buf_clear_highlight. If the highlight never will be
+ manually deleted pass in -1 for "src_id".
+
+ If "hl_group" is the empty string no highlight is added, but a
+ new src_id is still returned. This is useful for an external
+ plugin to synchrounously request an unique src_id at
+ initialization, and later asynchronously add and clear
+ highlights in response to buffer changes.
+
+ Parameters:~
+ {buffer} Buffer handle
+ {src_id} Source group to use or 0 to use a new group,
+ or -1 for ungrouped highlight
+ {hl_group} Name of the highlight group to use
+ {line} Line to highlight
+ {col_start} Start of range of columns to highlight
+ {col_end} End of range of columns to highlight, or -1
+ to highlight to end of line
+
+ Return:~
+ The src_id that was used
+
+ *nvim_buf_clear_highlight()*
+nvim_buf_clear_highlight({buffer}, {src_id}, {line_start}, {line_end})
+ Clears highlights from a given source group and a range of
+ lines
+
+ To clear a source group in the entire buffer, pass in 1 and -1
+ to line_start and line_end respectively.
+
+ Parameters:~
+ {buffer} Buffer handle
+ {src_id} Highlight source group to clear, or -1 to
+ clear all.
+ {line_start} Start of range of lines to clear
+ {line_end} End of range of lines to clear (exclusive)
+ or -1 to clear to end of file.
+
+
+==============================================================================
+3. Window Functions *api-window*
+
+nvim_win_get_buf({window}) *nvim_win_get_buf()*
+ Gets the current buffer in a window
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ Buffer handle
+
+nvim_win_get_cursor({window}) *nvim_win_get_cursor()*
+ Gets the cursor position in the window
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ (row, col) tuple
+
+nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()*
+ Sets the cursor position in the window
+
+ Parameters:~
+ {window} Window handle
+ {pos} (row, col) tuple representing the new position
+
+nvim_win_get_height({window}) *nvim_win_get_height()*
+ Gets the window height
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ Height as a count of rows
+
+nvim_win_set_height({window}, {height}) *nvim_win_set_height()*
+ Sets the window height. This will only succeed if the screen
+ is split horizontally.
+
+ Parameters:~
+ {window} Window handle
+ {height} Height as a count of rows
+
+nvim_win_get_width({window}) *nvim_win_get_width()*
+ Gets the window width
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ Width as a count of columns
+
+nvim_win_set_width({window}, {width}) *nvim_win_set_width()*
+ Sets the window width. This will only succeed if the screen is
+ split vertically.
+
+ Parameters:~
+ {window} Window handle
+ {width} Width as a count of columns
+
+nvim_win_get_var({window}, {name}) *nvim_win_get_var()*
+ Gets a window-scoped (w:) variable
+
+ Parameters:~
+ {window} Window handle
+ {name} Variable name
+
+ Return:~
+ Variable value
+
+nvim_win_set_var({window}, {name}, {value}) *nvim_win_set_var()*
+ Sets a window-scoped (w:) variable
+
+ Parameters:~
+ {window} Window handle
+ {name} Variable name
+ {value} Variable value
+
+nvim_win_del_var({window}, {name}) *nvim_win_del_var()*
+ Removes a window-scoped (w:) variable
+
+ Parameters:~
+ {window} Window handle
+ {name} Variable name
+
+nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
+ Gets a window option value
+
+ Parameters:~
+ {window} Window handle
+ {name} Option name
+
+ Return:~
+ Option value
+
+nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()*
+ Sets a window option value. Passing 'nil' as value deletes the
+ option(only works if there's a global fallback)
+
+ Parameters:~
+ {window} Window handle
+ {name} Option name
+ {value} Option value
+
+nvim_win_get_position({window}) *nvim_win_get_position()*
+ Gets the window position in display cells. First position is
+ zero.
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ (row, col) tuple with the window position
+
+nvim_win_get_tabpage({window}) *nvim_win_get_tabpage()*
+ Gets the window tabpage
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ Tabpage that contains the window
+
+nvim_win_get_number({window}) *nvim_win_get_number()*
+ Gets the window number
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ Window number
+
+nvim_win_is_valid({window}) *nvim_win_is_valid()*
+ Checks if a window is valid
+
+ Parameters:~
+ {window} Window handle
+
+ Return:~
+ true if the window is valid, false otherwise
+
+
+==============================================================================
+4. Tabpage Functions *api-tabpage*
+
+nvim_tabpage_list_wins({tabpage}) *nvim_tabpage_list_wins()*
+ Gets the windows in a tabpage
+
+ Parameters:~
+ {tabpage} Tabpage
+
+ Return:~
+ List of windows in tabpage
+
+nvim_tabpage_get_var({tabpage}, {name}) *nvim_tabpage_get_var()*
+ Gets a tab-scoped (t:) variable
+
+ Parameters:~
+ {tabpage} Tabpage handle
+ {name} Variable name
+
+ Return:~
+ Variable value
+
+nvim_tabpage_set_var({tabpage}, {name}, {value}) *nvim_tabpage_set_var()*
+ Sets a tab-scoped (t:) variable
+
+ Parameters:~
+ {tabpage} Tabpage handle
+ {name} Variable name
+ {value} Variable value
+
+nvim_tabpage_del_var({tabpage}, {name}) *nvim_tabpage_del_var()*
+ Removes a tab-scoped (t:) variable
+
+ Parameters:~
+ {tabpage} Tabpage handle
+ {name} Variable name
+
+nvim_tabpage_get_win({tabpage}) *nvim_tabpage_get_win()*
+ Gets the current window in a tabpage
+
+ Parameters:~
+ {tabpage} Tabpage handle
+
+ Return:~
+ Window handle
+
+nvim_tabpage_get_number({tabpage}) *nvim_tabpage_get_number()*
+ Gets the tabpage number
+
+ Parameters:~
+ {tabpage} Tabpage handle
+
+ Return:~
+ Tabpage number
+
+nvim_tabpage_is_valid({tabpage}) *nvim_tabpage_is_valid()*
+ Checks if a tabpage is valid
+
+ Parameters:~
+ {tabpage} Tabpage handle
+
+ Return:~
+ true if the tabpage is valid, false otherwise
+
+
+==============================================================================
+5. UI Functions *api-ui*
+
+remote_ui_disconnect() *remote_ui_disconnect()*
+ TODO: Documentation
+
+nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()*
+ TODO: Documentation
+
+nvim_ui_detach() *nvim_ui_detach()*
+ TODO: Documentation
+
+nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()*
+ TODO: Documentation
+
+nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
+ TODO: Documentation
+
+ vim:tw=78:ts=8:ft=help:norl: \ No newline at end of file
diff --git a/runtime/syntax/help.vim b/runtime/syntax/help.vim
index b3c7f2a63e..41bb0b1938 100644
--- a/runtime/syntax/help.vim
+++ b/runtime/syntax/help.vim
@@ -59,7 +59,7 @@ syn match helpSpecial "\[N]"
syn match helpSpecial "N N"he=s+1
syn match helpSpecial "Nth"me=e-2
syn match helpSpecial "N-1"me=e-2
-syn match helpSpecial "{[-a-zA-Z0-9'"*+/:%#=[\]<>.,]\+}"
+syn match helpSpecial "{[-_a-zA-Z0-9'"*+/:%#=[\]<>.,]\+}"
syn match helpSpecial "\s\[[-a-z^A-Z0-9_]\{2,}]"ms=s+1
syn match helpSpecial "<[-a-zA-Z0-9_]\+>"
syn match helpSpecial "<[SCM]-.>"
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index cd96a55eda..63618e902e 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -637,7 +637,9 @@ if !filereadable(s:perlpath)
endif
if g:vimsyn_embed =~# 'p' && filereadable(s:perlpath)
unlet! b:current_syntax
+ let s:foldmethod = &l:foldmethod
exe "syn include @vimPerlScript ".s:perlpath
+ let &l:foldmethod = s:foldmethod
VimFoldp syn region vimPerlRegion matchgroup=vimScriptDelim start=+pe\%[rl]\s*<<\s*\z(.*\)$+ end=+^\z1$+ contains=@vimPerlScript
VimFoldp syn region vimPerlRegion matchgroup=vimScriptDelim start=+pe\%[rl]\s*<<\s*$+ end=+\.$+ contains=@vimPerlScript
syn cluster vimFuncBodyList add=vimPerlRegion
@@ -659,7 +661,9 @@ if !filereadable(s:rubypath)
endif
if g:vimsyn_embed =~# 'r' && filereadable(s:rubypath)
unlet! b:current_syntax
+ let s:foldmethod = &l:foldmethod
exe "syn include @vimRubyScript ".s:rubypath
+ let &l:foldmethod = s:foldmethod
VimFoldr syn region vimRubyRegion matchgroup=vimScriptDelim start=+rub[y]\s*<<\s*\z(.*\)$+ end=+^\z1$+ contains=@vimRubyScript
syn region vimRubyRegion matchgroup=vimScriptDelim start=+rub[y]\s*<<\s*$+ end=+\.$+ contains=@vimRubyScript
syn cluster vimFuncBodyList add=vimRubyRegion
diff --git a/scripts/gen_api_vimdoc.py b/scripts/gen_api_vimdoc.py
new file mode 100644
index 0000000000..d7165187f4
--- /dev/null
+++ b/scripts/gen_api_vimdoc.py
@@ -0,0 +1,514 @@
+#!/usr/bin/env python
+"""Parses Doxygen XML output to generate Neovim's API documentation.
+
+This would be easier using lxml and XSLT, but:
+
+ 1. This should avoid needing Python dependencies, especially ones that are
+ C modules that have library dependencies (lxml requires libxml and
+ libxslt).
+ 2. I wouldn't know how to deal with nested indentation in <para> tags using
+ XSLT.
+
+Each function documentation is formatted with the following rules:
+
+ - Maximum width of 78 characters (`text_width`).
+ - Spaces for indentation.
+ - Function signature and helptag are on the same line.
+ - Helptag is right aligned.
+ - Signature and helptag must have a minimum of 8 spaces between them.
+ - If the signature is too long, it is placed on the line after the
+ helptag. The signature wraps at `text_width - 8` characters with
+ subsequent lines indented to the open parenthesis.
+ - Documentation body will be indented by 16 spaces.
+ - Subsection bodies are indented an additional 4 spaces.
+ - Documentation body consists of the function description, parameter details,
+ return description, and C declaration.
+ - Parameters are omitted for the `void` and `Error *` types, or if the
+ parameter is marked as [out].
+ - Each function documentation is separated by a single line.
+
+The C declaration is added to the end to show actual argument types.
+"""
+import os
+import re
+import sys
+import shutil
+import textwrap
+import subprocess
+
+from xml.dom import minidom
+
+# Text at the top of the doc file.
+preamble = '''
+Note: This documentation is generated from Neovim's API source code.
+'''
+
+doc_filename = 'api-funcs.txt'
+
+# Section name overrides.
+section_name = {
+ 'vim.c': 'Global',
+}
+
+# Section ordering.
+section_order = (
+ 'vim.c',
+ 'buffer.c',
+ 'window.c',
+ 'tabpage.c',
+ 'ui.c',
+)
+
+param_exclude = (
+ 'channel_id',
+)
+
+text_width = 78
+script_path = os.path.abspath(__file__)
+base_dir = os.path.dirname(os.path.dirname(script_path))
+src_dir = os.path.join(base_dir, 'src/nvim/api')
+out_dir = os.path.join(base_dir, 'tmp/api_doc')
+filter_cmd = '%s %s' % (sys.executable, script_path)
+seen_funcs = set()
+
+# Tracks `xrefsect` titles. As of this writing, used only for separating
+# deprecated functions.
+xrefs = set()
+
+
+# XML Parsing Utilities {{{
+def find_first(parent, name):
+ """Finds the first matching node within parent."""
+ sub = parent.getElementsByTagName(name)
+ if not sub:
+ return None
+ return sub[0]
+
+
+def get_children(parent, name):
+ """Yield matching child nodes within parent."""
+ for child in parent.childNodes:
+ if child.nodeType == child.ELEMENT_NODE and child.nodeName == name:
+ yield child
+
+
+def get_child(parent, name):
+ """Get the first matching child node."""
+ for child in get_children(parent, name):
+ return child
+ return None
+
+
+def clean_text(text):
+ """Cleans text.
+
+ Only cleans superfluous whitespace at the moment.
+ """
+ return ' '.join(text.split()).strip()
+
+
+def clean_lines(text):
+ """Removes superfluous lines.
+
+ The beginning and end of the string is trimmed. Empty lines are collapsed.
+ """
+ return re.sub(r'\A\n\s*\n*|\n\s*\n*\Z', '', re.sub(r'(\n\s*\n+)+', '\n\n', text))
+
+
+def get_text(parent):
+ """Combine all text in a node."""
+ if parent.nodeType == parent.TEXT_NODE:
+ return parent.data
+
+ out = ''
+ for node in parent.childNodes:
+ if node.nodeType == node.TEXT_NODE:
+ out += clean_text(node.data)
+ elif node.nodeType == node.ELEMENT_NODE:
+ out += ' ' + get_text(node)
+ return out
+
+
+def doc_wrap(text, prefix='', width=70, func=False):
+ """Wraps text to `width`.
+
+ The first line is prefixed with `prefix`, and subsequent lines are aligned.
+ If `func` is True, only wrap at commas.
+ """
+ if not width:
+ return text
+
+ indent_space = ' ' * len(prefix)
+
+ if func:
+ lines = [prefix]
+ for part in text.split(', '):
+ if part[-1] not in ');':
+ part += ', '
+ if len(lines[-1]) + len(part) > width:
+ lines.append(indent_space)
+ lines[-1] += part
+ return '\n'.join(x.rstrip() for x in lines).rstrip()
+
+ return '\n'.join(textwrap.wrap(text.strip(), width=width,
+ initial_indent=prefix,
+ subsequent_indent=indent_space))
+
+
+def parse_params(parent, width=62):
+ """Parse Doxygen `parameterlist`."""
+ name_length = 0
+ items = []
+ for child in parent.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ continue
+
+ name_node = find_first(child, 'parametername')
+ if name_node.getAttribute('direction') == 'out':
+ continue
+
+ name = get_text(name_node)
+ if name in param_exclude:
+ continue
+
+ name = '{%s}' % name
+ name_length = max(name_length, len(name) + 2)
+
+ desc = ''
+ desc_node = get_child(child, 'parameterdescription')
+ if desc_node:
+ desc = parse_parblock(desc_node, width=None)
+ items.append((name.strip(), desc.strip()))
+
+ out = 'Parameters:~\n'
+ for name, desc in items:
+ name = ' %s' % name.ljust(name_length)
+ out += doc_wrap(desc, prefix=name, width=width) + '\n'
+ return out.strip()
+
+
+def parse_para(parent, width=62):
+ """Parse doxygen `para` tag.
+
+ I assume <para> is a paragraph block or "a block of text". It can contain
+ text nodes, or other tags.
+ """
+ line = ''
+ lines = []
+ for child in parent.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ line += child.data
+ elif child.nodeName == 'computeroutput':
+ line += '`%s`' % get_text(child)
+ else:
+ if line:
+ lines.append(doc_wrap(line, width=width))
+ line = ''
+
+ if child.nodeName == 'parameterlist':
+ lines.append(parse_params(child, width=width))
+ elif child.nodeName == 'xrefsect':
+ title = get_text(get_child(child, 'xreftitle'))
+ xrefs.add(title)
+ xrefdesc = parse_para(get_child(child, 'xrefdescription'))
+ lines.append(doc_wrap(xrefdesc, prefix='%s: ' % title,
+ width=width) + '\n')
+ elif child.nodeName == 'simplesect':
+ kind = child.getAttribute('kind')
+ if kind == 'return':
+ lines.append('%s:~' % kind.title())
+ lines.append(doc_wrap(parse_para(child),
+ prefix=' ',
+ width=width))
+ else:
+ lines.append(get_text(child))
+
+ if line:
+ lines.append(doc_wrap(line, width=width))
+ return clean_lines('\n'.join(lines).strip())
+
+
+def parse_parblock(parent, width=62):
+ """Parses a nested block of `para` tags.
+
+ Named after the \parblock command, but not directly related.
+ """
+ paragraphs = []
+ for child in parent.childNodes:
+ if child.nodeType == child.TEXT_NODE:
+ paragraphs.append(doc_wrap(child.data, width=width))
+ elif child.nodeName == 'para':
+ paragraphs.append(parse_para(child, width=width))
+ else:
+ paragraphs.append(doc_wrap(get_text(child), width=width))
+ paragraphs.append('')
+ return clean_lines('\n'.join(paragraphs).strip())
+# }}}
+
+
+def parse_source_xml(filename):
+ """Collects API functions.
+
+ This returns two strings:
+ 1. The API functions
+ 2. The deprecated API functions
+
+ The caller decides what to do with the deprecated documentation.
+ """
+ global xrefs
+ xrefs = set()
+ functions = []
+ deprecated_functions = []
+
+ dom = minidom.parse(filename)
+ for member in dom.getElementsByTagName('memberdef'):
+ if member.getAttribute('static') == 'yes' or \
+ member.getAttribute('kind') != 'function':
+ continue
+
+ loc = find_first(member, 'location')
+ if 'private' in loc.getAttribute('file'):
+ continue
+
+ return_type = get_text(get_child(member, 'type'))
+ if return_type == '':
+ continue
+
+ if return_type.startswith(('ArrayOf', 'DictionaryOf')):
+ parts = return_type.strip('_').split('_')
+ return_type = '%s(%s)' % (parts[0], ', '.join(parts[1:]))
+
+ name = get_text(get_child(member, 'name'))
+
+ vimtag = '*%s()*' % name
+ args = []
+ type_length = 0
+
+ for param in get_children(member, 'param'):
+ arg_type = get_text(get_child(param, 'type')).strip()
+ arg_name = ''
+ declname = get_child(param, 'declname')
+ if declname:
+ arg_name = get_text(declname).strip()
+
+ if arg_name in param_exclude:
+ continue
+
+ if arg_type.endswith('*'):
+ arg_type = arg_type.strip('* ')
+ arg_name = '*' + arg_name
+ type_length = max(type_length, len(arg_type))
+ args.append((arg_type, arg_name))
+
+ c_args = []
+ for arg_type, arg_name in args:
+ c_args.append(' ' + (
+ '%s %s' % (arg_type.ljust(type_length), arg_name)).strip())
+
+ c_decl = textwrap.indent('%s %s(\n%s\n);' % (return_type, name,
+ ',\n'.join(c_args)),
+ ' ')
+
+ prefix = '%s(' % name
+ suffix = '%s)' % ', '.join('{%s}' % a[1] for a in args
+ if a[0] not in ('void', 'Error'))
+
+ # Minimum 8 chars between signature and vimtag
+ lhs = (text_width - 8) - len(prefix)
+
+ if len(prefix) + len(suffix) > lhs:
+ signature = vimtag.rjust(text_width) + '\n'
+ signature += doc_wrap(suffix, width=text_width-8, prefix=prefix,
+ func=True)
+ else:
+ signature = prefix + suffix
+ signature += vimtag.rjust(text_width - len(signature))
+
+ doc = ''
+ desc = find_first(member, 'detaileddescription')
+ if desc:
+ doc = parse_parblock(desc)
+ if 'DEBUG' in os.environ:
+ print(textwrap.indent(
+ re.sub(r'\n\s*\n+', '\n',
+ desc.toprettyxml(indent=' ', newl='\n')), ' ' * 16))
+
+ if not doc:
+ doc = 'TODO: Documentation'
+
+ if 'INCLUDE_C_DECL' in os.environ:
+ doc += '\n\nC Declaration:~\n>\n'
+ doc += c_decl
+ doc += '\n<'
+
+ func_doc = signature + '\n'
+ func_doc += textwrap.indent(clean_lines(doc), ' ' * 16)
+ func_doc = re.sub(r'^\s+([<>])$', r'\1', func_doc, flags=re.M)
+
+ if 'Deprecated' in xrefs:
+ deprecated_functions.append(func_doc)
+ else:
+ functions.append(func_doc)
+
+ xrefs.clear()
+
+ return '\n\n'.join(functions), '\n\n'.join(deprecated_functions)
+
+
+def gen_docs(config):
+ """Generate documentation.
+
+ Doxygen is called and configured through stdin.
+ """
+ p = subprocess.Popen(['doxygen', '-'], stdin=subprocess.PIPE)
+ p.communicate(config.format(input=src_dir, output=out_dir,
+ filter=filter_cmd).encode('utf8'))
+ if p.returncode:
+ sys.exit(p.returncode)
+
+ title_length = 0
+ sections = {}
+ sep = '=' * text_width
+
+ base = os.path.join(out_dir, 'xml')
+ dom = minidom.parse(os.path.join(base, 'index.xml'))
+ for compound in dom.getElementsByTagName('compound'):
+ if compound.getAttribute('kind') != 'file':
+ continue
+
+ filename = get_text(find_first(compound, 'name'))
+ if filename.endswith('.c'):
+ functions, deprecated = parse_source_xml(
+ os.path.join(base, '%s.xml' % compound.getAttribute('refid')))
+
+ if not functions and not deprecated:
+ continue
+
+ if functions or deprecated:
+ name = os.path.splitext(os.path.basename(filename))[0]
+ if name == 'ui':
+ name = name.upper()
+ else:
+ name = name.title()
+
+ doc = ''
+ if functions:
+ doc += '\n\n' + functions
+
+ if 'INCLUDE_DEPRECATED' in os.environ and deprecated:
+ doc += '\n\n\nDeprecated %s Functions:~\n\n' % name
+ doc += deprecated
+
+ if doc:
+ filename = os.path.basename(filename)
+ name = section_name.get(filename, name)
+ title = '%s Functions' % name
+ helptag = '*api-%s*' % name.lower()
+ title_length = max(title_length, len(title))
+ sections[filename] = (title, helptag, doc)
+
+ if not sections:
+ return
+
+ title_left = '*%s*' % doc_filename
+ title_center = 'Neovim API Function Reference'
+ title_right = '{Nvim}'
+ margin = max(len(title_left), len(title_right))
+ head = (title_left.ljust(margin) +
+ title_center.center(text_width - margin * 2) +
+ title_right.rjust(margin)) + '\n'
+
+ head += '\n%s\n\n' % doc_wrap(preamble, width=text_width)
+ head += 'Contents:\n\n'
+
+ docs = ''
+
+ title_length += len(str(len(section_order))) + 2
+ i = 0
+ for filename in section_order:
+ if filename not in sections:
+ continue
+ title, helptag, section_doc = sections.pop(filename)
+
+ i += 1
+ docs += sep
+ title = '%d. %s' % (i, title)
+ head += (title.ljust(title_length) + ' ' +
+ helptag.replace('*', '|') + '\n')
+ docs += '\n%s%s' % (title, helptag.rjust(text_width - len(title)))
+ docs += section_doc
+ docs += '\n\n\n'
+
+ if sections:
+ # In case new API sources are added without updating the order dict.
+ for title, helptag, section_doc in sections.values():
+ i += 1
+ docs += sep
+ title = '%d. %s' % (i, title)
+ head += (title.ljust(title_length) + ' ' +
+ helptag.replace('*', '|') + '\n')
+ docs += '\n%s%s' % (title, helptag.rjust(text_width - len(title)))
+ docs += section_doc
+ docs += '\n\n\n'
+
+ docs = '%s\n%s' % (head, docs)
+ docs = docs.rstrip() + '\n\n'
+ docs += ' vim:tw=78:ts=8:ft=help:norl:'
+
+ doc_file = os.path.join(base_dir, 'runtime/doc', doc_filename)
+ with open(doc_file, 'wb') as fp:
+ fp.write(docs.encode('utf8'))
+
+ shutil.rmtree(out_dir)
+
+
+def filter_source(filename):
+ """Filters the source to fix macros that confuse Doxygen."""
+ with open(filename, 'rt') as fp:
+ print(re.sub(r'^(ArrayOf|DictionaryOf)(\(.*?\))',
+ lambda m: m.group(1)+'_'.join(
+ re.split(r'[^\w]+', m.group(2))),
+ fp.read(), flags=re.M))
+
+
+# Doxygen Config {{{
+Doxyfile = '''
+OUTPUT_DIRECTORY = {output}
+INPUT = {input}
+INPUT_ENCODING = UTF-8
+FILE_PATTERNS = *.h *.c
+RECURSIVE = YES
+INPUT_FILTER = "{filter}"
+EXCLUDE =
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS = */private/*
+EXCLUDE_SYMBOLS =
+
+GENERATE_HTML = NO
+GENERATE_DOCSET = NO
+GENERATE_HTMLHELP = NO
+GENERATE_QHP = NO
+GENERATE_TREEVIEW = NO
+GENERATE_LATEX = NO
+GENERATE_RTF = NO
+GENERATE_MAN = NO
+GENERATE_DOCBOOK = NO
+GENERATE_AUTOGEN_DEF = NO
+
+GENERATE_XML = YES
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = NO
+
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = YES
+EXPAND_ONLY_PREDEF = NO
+'''
+# }}}
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1:
+ filter_source(sys.argv[1])
+ else:
+ gen_docs(Doxyfile)
+
+# vim: set ft=python ts=4 sw=4 tw=79 et fdm=marker :
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index e5858b779a..2ca621ad22 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5162,12 +5162,18 @@ dict_equal (
dictitem_T *item2;
int todo;
- if (d1 == NULL || d2 == NULL)
- return FALSE;
- if (d1 == d2)
- return TRUE;
- if (dict_len(d1) != dict_len(d2))
- return FALSE;
+ if (d1 == NULL && d2 == NULL) {
+ return true;
+ }
+ if (d1 == NULL || d2 == NULL) {
+ return false;
+ }
+ if (d1 == d2) {
+ return true;
+ }
+ if (dict_len(d1) != dict_len(d2)) {
+ return false;
+ }
todo = (int)d1->dv_hashtab.ht_used;
for (hi = d1->dv_hashtab.ht_array; todo > 0; ++hi) {
@@ -6669,9 +6675,12 @@ dictitem_T *dict_find(dict_T *d, char_u *key, int len)
char_u *tofree = NULL;
hashitem_T *hi;
- if (len < 0)
+ if (d == NULL) {
+ return NULL;
+ }
+ if (len < 0) {
akey = key;
- else if (len >= AKEYLEN) {
+ } else if (len >= AKEYLEN) {
tofree = akey = vim_strnsave(key, len);
} else {
/* Avoid a malloc/free by using buf[]. */
@@ -10067,7 +10076,7 @@ static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} else if (STRCMP(varname, "changedtick") == 0) {
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = curbuf->b_changedtick;
- done = TRUE;
+ done = true;
} else {
/* Look up the variable. */
/* Let getbufvar({nr}, "") return the "b:" dictionary. */
@@ -15064,7 +15073,6 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
buf_T *buf;
- aco_save_T aco;
char_u *varname, *bufvarname;
typval_T *varp;
char_u nbuf[NUMBUFLEN];
@@ -15077,29 +15085,34 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr)
varp = &argvars[2];
if (buf != NULL && varname != NULL && varp != NULL) {
- /* set curbuf to be our buf, temporarily */
- aucmd_prepbuf(&aco, buf);
-
if (*varname == '&') {
long numval;
char_u *strval;
- int error = FALSE;
+ int error = false;
+ aco_save_T aco;
+
+ // set curbuf to be our buf, temporarily
+ aucmd_prepbuf(&aco, buf);
++varname;
numval = get_tv_number_chk(varp, &error);
strval = get_tv_string_buf_chk(varp, nbuf);
if (!error && strval != NULL)
set_option_value(varname, numval, strval, OPT_LOCAL);
+
+ // reset notion of buffer
+ aucmd_restbuf(&aco);
} else {
+ buf_T *save_curbuf = curbuf;
+
bufvarname = xmalloc(STRLEN(varname) + 3);
+ curbuf = buf;
STRCPY(bufvarname, "b:");
STRCPY(bufvarname + 2, varname);
set_var(bufvarname, varp, TRUE);
xfree(bufvarname);
+ curbuf = save_curbuf;
}
-
- /* reset notion of buffer */
- aucmd_restbuf(&aco);
}
}
@@ -15516,7 +15529,10 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
if (argvars[1].v_type == VAR_LIST) {
- int len = argvars[1].vval.v_list->lv_len;
+ list_T *ll = argvars[1].vval.v_list;
+ // If the list is NULL handle like an empty list.
+ int len = ll == NULL ? 0 : ll->lv_len;
+
// First half: use for pointers to result lines; second half: use for
// pointers to allocated copies.
char_u **lstval = xmalloc(sizeof(char_u *) * ((len + 1) * 2));
@@ -15525,7 +15541,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr)
char_u **curallocval = allocval;
char_u buf[NUMBUFLEN];
- for (listitem_T *li = argvars[1].vval.v_list->lv_first;
+ for (listitem_T *li = ll == NULL ? NULL : ll->lv_first;
li != NULL;
li = li->li_next) {
char_u *strval = get_tv_string_buf_chk(&li->li_tv, buf);
@@ -21561,7 +21577,10 @@ void func_unref(char_u *name)
if (name != NULL && isdigit(*name)) {
fp = find_func(name);
if (fp == NULL) {
- EMSG2(_(e_intern2), "func_unref()");
+#ifdef EXITFREE
+ if (!entered_free_all_mem) // NOLINT(readability/braces)
+#endif
+ EMSG2(_(e_intern2), "func_unref()");
} else {
user_func_unref(fp);
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index fbffc2d44d..872ff8d5b8 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -636,10 +636,14 @@ EXTERN int exiting INIT(= FALSE);
/* TRUE when planning to exit Vim. Might
* still keep on running if there is a changed
* buffer. */
-/* volatile because it is used in signal handler deathtrap(). */
-EXTERN volatile int full_screen INIT(= FALSE);
-/* TRUE when doing full-screen output
- * otherwise only writing some messages */
+#if defined(EXITFREE)
+// true when in or after free_all_mem()
+EXTERN bool entered_free_all_mem INIT(= false);
+#endif
+// volatile because it is used in signal handler deathtrap().
+EXTERN volatile int full_screen INIT(= false);
+// TRUE when doing full-screen output
+// otherwise only writing some messages
EXTERN int restricted INIT(= FALSE);
// TRUE when started in restricted mode (-Z)
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 071ef335cf..1884d55999 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -488,13 +488,13 @@ void time_to_bytes(time_t time_, uint8_t buf[8])
void free_all_mem(void)
{
buf_T *buf, *nextbuf;
- static bool entered = false;
- /* When we cause a crash here it is caught and Vim tries to exit cleanly.
- * Don't try freeing everything again. */
- if (entered)
+ // When we cause a crash here it is caught and Vim tries to exit cleanly.
+ // Don't try freeing everything again.
+ if (entered_free_all_mem) {
return;
- entered = true;
+ }
+ entered_free_all_mem = true;
// Don't want to trigger autocommands from here on.
block_autocmds();
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 571a37c62c..7483973fca 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -81,3 +81,14 @@ func Test_loop_over_null_list()
call assert_true(0, 'should not get here')
endfor
endfunc
+
+func Test_compare_null_dict()
+ call assert_fails('let x = v:_null_dict[10]')
+ call assert_equal({}, {})
+ call assert_equal(v:_null_dict, v:_null_dict)
+ call assert_notequal({}, v:_null_dict)
+endfunc
+
+func Test_set_reg_null_list()
+ call setreg('x', v:_null_list)
+endfunc
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 06ddbd5306..6914cd9409 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -242,7 +242,7 @@ static int included_patches[] = {
2201,
// 2200,
// 2199 NA
- // 2198,
+ // 2198 NA
// 2197,
// 2196,
// 2195 NA
@@ -308,7 +308,7 @@ static int included_patches[] = {
// 2135,
// 2134,
// 2133 NA
- // 2132,
+ // 2132 NA
// 2131 NA
// 2130 NA
// 2129 NA
@@ -346,8 +346,8 @@ static int included_patches[] = {
// 2097,
// 2096,
// 2095,
- // 2094,
- // 2093,
+ // 2094 NA
+ // 2093 NA
// 2092 NA
// 2091 NA
// 2090,
@@ -367,7 +367,7 @@ static int included_patches[] = {
// 2076,
// 2075,
// 2074,
- // 2073,
+ // 2073 NA
// 2072,
2071,
// 2070 NA
@@ -397,7 +397,7 @@ static int included_patches[] = {
// 2046,
// 2045 NA
// 2044,
- // 2043,
+ 2043,
// 2042 NA
// 2041 NA
// 2040 NA
@@ -435,16 +435,16 @@ static int included_patches[] = {
// 2008,
2007,
// 2006,
- // 2005,
+ 2005,
// 2004 NA
// 2003 NA
// 2002,
// 2001 NA
- // 2000,
- // 1999,
+ 2000,
+ 1999,
// 1998 NA
1997,
- // 1996,
+ 1996,
// 1995 NA
// 1994,
// 1993,
@@ -468,7 +468,7 @@ static int included_patches[] = {
// 1975,
// 1974 NA
1973,
- // 1972,
+ // 1972 NA
1971,
1970,
// 1969 NA
@@ -485,7 +485,7 @@ static int included_patches[] = {
// 1958 NA
// 1957 NA
1956,
- // 1955,
+ // 1955 NA
// 1954,
1953,
1952,
@@ -504,7 +504,7 @@ static int included_patches[] = {
// 1939 NA
// 1938 NA
1937,
- // 1936,
+ // 1936 NA
// 1935 NA
// 1934 NA
// 1933 NA
@@ -529,15 +529,15 @@ static int included_patches[] = {
// 1914,
1913,
1912,
- // 1911,
+ // 1911 NA
// 1910,
1909,
// 1908 NA
- // 1907,
+ // 1907 NA
// 1906 NA
- // 1905,
- // 1904,
- // 1903,
+ // 1905 NA
+ // 1904 NA
+ // 1903 NA
// 1902 NA
// 1901 NA
1900,
@@ -552,7 +552,7 @@ static int included_patches[] = {
// 1891 NA
// 1890 NA
// 1889,
- // 1888,
+ // 1888 NA
// 1887 NA
// 1886 NA
// 1885 NA
@@ -572,14 +572,14 @@ static int included_patches[] = {
1871,
// 1870 NA
// 1869 NA
- // 1868,
+ 1868,
1867,
- // 1866,
+ 1866,
// 1865 NA
// 1864 NA
// 1863 NA
// 1862 NA
- // 1861,
+ 1861,
1860,
// 1859 NA
// 1858 NA
@@ -593,15 +593,15 @@ static int included_patches[] = {
// 1850 NA
// 1849 NA
// 1848 NA
- // 1847,
+ 1847,
// 1846 NA
// 1845 NA
- // 1844,
+ // 1844 NA
// 1843 NA
1842,
1841,
1840,
- // 1839,
+ 1839,
1838,
1837,
1836,
@@ -628,7 +628,7 @@ static int included_patches[] = {
1815,
// 1814 NA
1813,
- // 1812,
+ // 1812 NA
// 1811 NA
// 1810 NA
1809,
@@ -660,7 +660,7 @@ static int included_patches[] = {
// 1784 NA
1783,
1782,
- // 1781,
+ 1781,
1780,
1779,
// 1778 NA
@@ -672,15 +672,15 @@ static int included_patches[] = {
// 1772 NA
// 1771 NA
// 1770 NA
- // 1769,
+ // 1769 NA
1768,
// 1767 NA
// 1766 NA
1765,
// 1764 NA
1763,
- // 1762,
- // 1761,
+ // 1762 NA
+ // 1761 NA
// 1760 NA
1759,
1758,
@@ -715,7 +715,7 @@ static int included_patches[] = {
1730,
// 1729 NA
1728,
- // 1727,
+ // 1727 NA
// 1726 NA
// 1725 NA
// 1724 NA
@@ -786,7 +786,7 @@ static int included_patches[] = {
// 1659 NA
1658,
// 1657 NA
- // 1656,
+ 1656,
// 1655 NA
1654,
// 1653 NA
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 86fca401ac..ed153182ca 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -22,6 +22,9 @@ local nvim_argv = {nvim_prog, '-u', 'NONE', '-i', 'NONE', '-N',
local mpack = require('mpack')
+local tmpname = global_helpers.tmpname
+local uname = global_helpers.uname
+
-- Formulate a path to the directory containing nvim. We use this to
-- help run test executables. It helps to keep the tests working, even
-- when the build is not in the default location.
@@ -335,44 +338,6 @@ local function write_file(name, text, dont_dedent)
file:close()
end
--- Tries to get platform name from $SYSTEM_NAME, uname; fallback is "Windows".
-local uname = (function()
- local platform = nil
- return (function()
- if platform then
- return platform
- end
-
- platform = os.getenv("SYSTEM_NAME")
- if platform then
- return platform
- end
-
- local status, f = pcall(io.popen, "uname -s")
- if status then
- platform = f:read("*l")
- else
- platform = 'Windows'
- end
- return platform
- end)
-end)()
-
-local function tmpname()
- local fname = os.tmpname()
- if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
- -- In Windows tmpname() returns a filename starting with
- -- special sequence \s, prepend $TEMP path
- local tmpdir = os.getenv('TEMP')
- return tmpdir..fname
- elseif fname:match('^/tmp') and uname() == 'Darwin' then
- -- In OS X /tmp links to /private/tmp
- return '/private'..fname
- else
- return fname
- end
-end
-
local function source(code)
local fname = tmpname()
write_file(fname, code)
diff --git a/test/helpers.lua b/test/helpers.lua
index 4c50c7644f..0bc62da5d7 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -52,9 +52,49 @@ local function check_logs()
assert(0 == runtime_errors)
end
+-- Tries to get platform name from $SYSTEM_NAME, uname; fallback is "Windows".
+local uname = (function()
+ local platform = nil
+ return (function()
+ if platform then
+ return platform
+ end
+
+ platform = os.getenv("SYSTEM_NAME")
+ if platform then
+ return platform
+ end
+
+ local status, f = pcall(io.popen, "uname -s")
+ if status then
+ platform = f:read("*l")
+ else
+ platform = 'Windows'
+ end
+ return platform
+ end)
+end)()
+
+local function tmpname()
+ local fname = os.tmpname()
+ if uname() == 'Windows' and fname:sub(1, 2) == '\\s' then
+ -- In Windows tmpname() returns a filename starting with
+ -- special sequence \s, prepend $TEMP path
+ local tmpdir = os.getenv('TEMP')
+ return tmpdir..fname
+ elseif fname:match('^/tmp') and uname() == 'Darwin' then
+ -- In OS X /tmp links to /private/tmp
+ return '/private'..fname
+ else
+ return fname
+ end
+end
+
return {
eq = eq,
neq = neq,
ok = ok,
check_logs = check_logs,
+ uname = uname,
+ tmpname = tmpname,
}
diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua
index 00637e0b8d..e288081960 100644
--- a/test/unit/formatc.lua
+++ b/test/unit/formatc.lua
@@ -219,13 +219,7 @@ local function standalone(...) -- luacheck: ignore
Preprocess.add_to_include_path('./../../build/include')
Preprocess.add_to_include_path('./../../.deps/usr/include')
- local input = Preprocess.preprocess_stream(arg[1])
- local raw = input:read('*all')
- input:close()
-
- if raw == nil then
- print("ERROR: Preprocess.preprocess_stream():read() returned empty")
- end
+ local raw = Preprocess.preprocess('', arg[1])
local formatted
if #arg == 2 and arg[2] == 'no' then
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 3564f76442..a485a875ab 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -45,6 +45,8 @@ local function filter_complex_blocks(body)
return table.concat(result, "\n")
end
+local previous_defines = ''
+
-- use this helper to import C files, you can pass multiple paths at once,
-- this helper will return the C namespace of the nvim library.
local function cimport(...)
@@ -66,17 +68,8 @@ local function cimport(...)
return libnvim
end
- local body = nil
- for _ = 1, 10 do
- local stream = Preprocess.preprocess_stream(unpack(paths))
- body = stream:read("*a")
- stream:close()
- if body ~= nil then break end
- end
-
- if body == nil then
- print("ERROR: helpers.lua: Preprocess.preprocess_stream():read() returned empty")
- end
+ local body
+ body, previous_defines = Preprocess.preprocess(previous_defines, unpack(paths))
-- format it (so that the lines are "unique" statements), also filter out
-- Objective-C blocks
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 10ba997758..8c2a5c73e5 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -7,22 +7,22 @@ local ccs = {}
local env_cc = os.getenv("CC")
if env_cc then
- table.insert(ccs, {path = "/usr/bin/env " .. tostring(env_cc), type = "gcc"})
+ table.insert(ccs, {path = {"/usr/bin/env", env_cc}, type = "gcc"})
end
if ffi.os == "Windows" then
- table.insert(ccs, {path = "cl", type = "msvc"})
+ table.insert(ccs, {path = {"cl"}, type = "msvc"})
end
-table.insert(ccs, {path = "/usr/bin/env cc", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc-4.9", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc-4.8", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env gcc-4.7", type = "gcc"})
-table.insert(ccs, {path = "/usr/bin/env clang", type = "clang"})
-table.insert(ccs, {path = "/usr/bin/env icc", type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "cc"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.9"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.8"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "gcc-4.7"}, type = "gcc"})
+table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"})
+table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"})
-local quote_me = '[^%w%+%-%=%@%_%/]' -- complement (needn't quote)
+local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote)
local function shell_quote(str)
if string.find(str, quote_me) or str == '' then
return "'" .. string.gsub(str, "'", [['"'"']]) .. "'"
@@ -61,12 +61,12 @@ end
-- will produce a string that represents a meta C header file that includes
-- all the passed in headers. I.e.:
--
--- headerize({"stdio.h", "math.h", true}
+-- headerize({"stdio.h", "math.h"}, true)
-- produces:
-- #include <stdio.h>
-- #include <math.h>
--
--- headerize({"vim.h", "memory.h", false}
+-- headerize({"vim.h", "memory.h"}, false)
-- produces:
-- #include "vim.h"
-- #include "memory.h"
@@ -79,8 +79,7 @@ local function headerize(headers, global)
end
local formatted = {}
- for i = 1, #headers do
- local hdr = headers[i]
+ for _, hdr in ipairs(headers) do
formatted[#formatted + 1] = "#include " ..
tostring(pre) ..
tostring(hdr) ..
@@ -91,49 +90,77 @@ local function headerize(headers, global)
end
local Gcc = {
+ preprocessor_extra_flags = {},
+ get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
+ get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
+}
+
+function Gcc:define(name, args, val)
+ local define = '-D' .. name
+ if args ~= nil then
+ define = define .. '(' .. table.concat(args, ',') .. ')'
+ end
+ if val ~= nil then
+ define = define .. '=' .. val
+ end
+ self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define
+end
+
+function Gcc:undefine(name)
+ self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = (
+ '-U' .. name)
+end
+
+function Gcc:init_defines()
-- preprocessor flags that will hopefully make the compiler produce C
-- declarations that the LuaJIT ffi understands.
- preprocessor_extra_flags = {
- '-D "aligned(ARGS)="',
- '-D "__attribute__(ARGS)="',
- '-D "__asm(ARGS)="',
- '-D "__asm__(ARGS)="',
- '-D "__inline__="',
- '-D "EXTERN=extern"',
- '-D "INIT(...)="',
- '-D_GNU_SOURCE',
- '-DINCLUDE_GENERATED_DECLARATIONS',
-
- -- Needed for FreeBSD
- '-D "_Thread_local="',
-
- -- Needed for macOS Sierra
- '-D "_Nullable="',
- '-D "_Nonnull="',
- '-U__BLOCKS__',
- }
-}
+ self:define('aligned', {'ARGS'}, '')
+ self:define('__attribute__', {'ARGS'}, '')
+ self:define('__asm', {'ARGS'}, '')
+ self:define('__asm__', {'ARGS'}, '')
+ self:define('__inline__', nil, '')
+ self:define('EXTERN', nil, 'extern')
+ self:define('INIT', {'...'}, '')
+ self:define('_GNU_SOURCE')
+ self:define('INCLUDE_GENERATED_DECLARATIONS')
+ -- Needed for FreeBSD
+ self:define('_Thread_local', nil, '')
+ -- Needed for macOS Sierra
+ self:define('_Nullable', nil, '')
+ self:define('_Nonnull', nil, '')
+ self:undefine('__BLOCKS__')
+end
function Gcc:new(obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
+ self:init_defines()
return obj
end
function Gcc:add_to_include_path(...)
- local paths = {...}
- for i = 1, #paths do
- local path = paths[i]
- local directive = '-I ' .. '"' .. path .. '"'
+ for i = 1, select('#', ...) do
+ local path = select(i, ...)
local ef = self.preprocessor_extra_flags
- ef[#ef + 1] = directive
+ ef[#ef + 1] = '-I' .. path
+ end
+end
+
+local function argss_to_cmd(...)
+ local cmd = ''
+ for i = 1, select('#', ...) do
+ for _, arg in ipairs(select(i, ...)) do
+ cmd = cmd .. ' ' .. shell_quote(arg)
+ end
end
+ return cmd
end
-- returns a list of the headers files upon which this file relies
function Gcc:dependencies(hdr)
- local out = io.popen(tostring(self.path) .. " -M " .. tostring(hdr) .. " 2>&1")
+ local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1'
+ local out = io.popen(cmd)
local deps = out:read("*a")
out:close()
if deps then
@@ -143,23 +170,51 @@ function Gcc:dependencies(hdr)
end
end
+local function repeated_call(...)
+ local cmd = argss_to_cmd(...)
+ for _ = 1, 10 do
+ local stream = io.popen(cmd)
+ local ret = stream:read('*a')
+ stream:close()
+ if ret then
+ return ret
+ end
+ end
+ print('ERROR: preprocess.lua: Failed to execute ' .. cmd .. ': nil return after 10 attempts')
+ return nil
+end
+
-- returns a stream representing a preprocessed form of the passed-in headers.
-- Don't forget to close the stream by calling the close() method on it.
-function Gcc:preprocess_stream(...)
+function Gcc:preprocess(previous_defines, ...)
-- create pseudo-header
local pseudoheader = headerize({...}, false)
- local defines = table.concat(self.preprocessor_extra_flags, ' ')
- local cmd = ("echo $hdr | " ..
- tostring(self.path) ..
- " " ..
- tostring(defines) ..
- " -std=c99 -P -E -"):gsub('$hdr', shell_quote(pseudoheader))
+ local pseudoheader_fname = 'tmp_pseudoheader.h'
+ local pseudoheader_file = io.open(pseudoheader_fname, 'w')
+ pseudoheader_file:write(previous_defines)
+ pseudoheader_file:write("\n")
+ pseudoheader_file:write(pseudoheader)
+ pseudoheader_file:flush()
+ pseudoheader_file:close()
+
+ local defines = repeated_call(self.path, self.preprocessor_extra_flags,
+ self.get_defines_extra_flags,
+ {pseudoheader_fname})
+
-- lfs = require("lfs")
-- print("CWD: #{lfs.currentdir!}")
-- print("CMD: #{cmd}")
-- io.stderr\write("CWD: #{lfs.currentdir!}\n")
-- io.stderr\write("CMD: #{cmd}\n")
- return io.popen(cmd)
+
+ local declarations = repeated_call(self.path, self.preprocessor_extra_flags,
+ self.get_declarations_extra_flags,
+ {pseudoheader_fname})
+
+ os.remove(pseudoheader_fname)
+
+ assert(declarations and defines)
+ return declarations, defines
end
local Clang = Gcc:new()
@@ -197,8 +252,8 @@ return {
includes = function(hdr)
return cc:dependencies(hdr)
end,
- preprocess_stream = function(...)
- return cc:preprocess_stream(...)
+ preprocess = function(...)
+ return cc:preprocess(...)
end,
add_to_include_path = function(...)
return cc:add_to_include_path(...)