Merge remote-tracking branch 'upstream/master'

This commit is contained in:
ckelsel 2017-08-21 09:19:08 +08:00
commit 0b6fa3a553
16 changed files with 270 additions and 155 deletions

View File

@ -48,17 +48,24 @@ and [more](https://github.com/neovim/neovim/wiki/Installing-Neovim)!
Project layout Project layout
-------------- --------------
├─ ci/ Build server scripts ├─ ci/ build automation
├─ cmake/ Build scripts ├─ cmake/ build scripts
├─ runtime/ User plugins/docs ├─ runtime/ user plugins/docs
├─ src/ Source code ├─ src/ application source code (see src/nvim/README.md)
├─ third-party/ CMake subproject to build dependencies │ ├─ api/ API subsystem
└─ test/ Test code │ ├─ eval/ VimL subsystem
│ ├─ event/ event-loop subsystem
│ ├─ generators/ code generation (pre-compilation)
│ ├─ lib/ generic data structures
│ ├─ lua/ lua subsystem
│ ├─ msgpack_rpc/ RPC subsystem
│ ├─ os/ low-level platform code
│ └─ tui/ built-in UI
├─ third-party/ cmake subproject to build dependencies
└─ test/ tests (see test/README.md)
- `third-party/` is activated if `USE_BUNDLED_DEPS` is undefined or the - To disable `third-party/` specify `USE_BUNDLED_DEPS=NO` or `USE_BUNDLED=NO`
`USE_BUNDLED` CMake option is true. (CMake option).
- [Source README](src/nvim/README.md)
- [Test README](test/README.md)
Features Features
-------- --------

View File

@ -90,7 +90,7 @@ endfunction
" Changes ':h clipboard' to ':help |clipboard|'. " Changes ':h clipboard' to ':help |clipboard|'.
function! s:help_to_link(s) abort function! s:help_to_link(s) abort
return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n]+)', ':help |\1|', 'g') return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g')
endfunction endfunction
" Format a message for a specific report item " Format a message for a specific report item

View File

@ -121,14 +121,14 @@ function! s:check_clipboard() abort
call health#report_start('Clipboard (optional)') call health#report_start('Clipboard (optional)')
let clipboard_tool = provider#clipboard#Executable() let clipboard_tool = provider#clipboard#Executable()
if empty(clipboard_tool) if exists('g:clipboard') && empty(clipboard_tool)
call health#report_warn(
\ 'No clipboard tool found. Clipboard registers will not work.',
\ [':help clipboard'])
elseif exists('g:clipboard') && (type({}) != type(g:clipboard)
\ || !has_key(g:clipboard, 'copy') || !has_key(g:clipboard, 'paste'))
call health#report_error( call health#report_error(
\ 'g:clipboard exists but is malformed. It must be a dictionary with the keys documented at :help g:clipboard') \ provider#clipboard#Error(),
\ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."])
elseif empty(clipboard_tool)
call health#report_warn(
\ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.',
\ [':help clipboard'])
else else
call health#report_ok('Clipboard tool found: '. clipboard_tool) call health#report_ok('Clipboard tool found: '. clipboard_tool)
endif endif

View File

@ -10,7 +10,7 @@ function! provider#stderr_collector(chan_id, data, event) dict
endfunction endfunction
function! provider#clear_stderr(chan_id) function! provider#clear_stderr(chan_id)
silent! call delete(s:stderr, a:chan_id) silent! call remove(s:stderr, a:chan_id)
endfunction endfunction
function! provider#get_stderr(chan_id) function! provider#get_stderr(chan_id)

View File

@ -3,6 +3,7 @@
" available. " available.
let s:copy = {} let s:copy = {}
let s:paste = {} let s:paste = {}
let s:clipboard = {}
" When caching is enabled, store the jobid of the xclip/xsel process keeping " When caching is enabled, store the jobid of the xclip/xsel process keeping
" ownership of the selection, so we know how long the cache is valid. " ownership of the selection, so we know how long the cache is valid.
@ -23,7 +24,7 @@ function! s:selection.on_exit(jobid, data, event) abort
call provider#clear_stderr(a:jobid) call provider#clear_stderr(a:jobid)
endfunction endfunction
let s:selections = { '*': s:selection, '+': copy(s:selection)} let s:selections = { '*': s:selection, '+': copy(s:selection) }
function! s:try_cmd(cmd, ...) abort function! s:try_cmd(cmd, ...) abort
let argv = split(a:cmd, " ") let argv = split(a:cmd, " ")
@ -55,9 +56,15 @@ endfunction
function! provider#clipboard#Executable() abort function! provider#clipboard#Executable() abort
if exists('g:clipboard') if exists('g:clipboard')
if type({}) isnot# type(g:clipboard)
\ || type({}) isnot# type(get(g:clipboard, 'copy', v:null))
\ || type({}) isnot# type(get(g:clipboard, 'paste', v:null))
let s:err = 'clipboard: invalid g:clipboard'
return ''
endif
let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null }) let s:copy = get(g:clipboard, 'copy', { '+': v:null, '*': v:null })
let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null }) let s:paste = get(g:clipboard, 'paste', { '+': v:null, '*': v:null })
let s:cache_enabled = get(g:clipboard, 'cache_enabled', 1) let s:cache_enabled = get(g:clipboard, 'cache_enabled', 0)
return get(g:clipboard, 'name', 'g:clipboard') return get(g:clipboard, 'name', 'g:clipboard')
elseif has('mac') && executable('pbcopy') elseif has('mac') && executable('pbcopy')
let s:copy['+'] = 'pbcopy' let s:copy['+'] = 'pbcopy'
@ -104,16 +111,17 @@ function! provider#clipboard#Executable() abort
return 'tmux' return 'tmux'
endif endif
let s:err = 'clipboard: No clipboard tool available. :help clipboard' let s:err = 'clipboard: No clipboard tool. :help clipboard'
return '' return ''
endfunction endfunction
if empty(provider#clipboard#Executable()) if empty(provider#clipboard#Executable())
" provider#clipboard#Call() *must not* be defined if the provider is broken.
" Otherwise eval_has_provider() thinks the clipboard provider is
" functioning, and eval_call_provider() will happily call it.
finish finish
endif endif
let s:clipboard = {}
function! s:clipboard.get(reg) abort function! s:clipboard.get(reg) abort
if s:selections[a:reg].owner > 0 if s:selections[a:reg].owner > 0
return s:selections[a:reg].data return s:selections[a:reg].data
@ -154,7 +162,9 @@ function! s:clipboard.set(lines, regtype, reg) abort
echohl WarningMsg echohl WarningMsg
echomsg 'clipboard: failed to execute: '.(s:copy[a:reg]) echomsg 'clipboard: failed to execute: '.(s:copy[a:reg])
echohl None echohl None
return 0
endif endif
return 1
endfunction endfunction
function! provider#clipboard#Call(method, args) abort function! provider#clipboard#Call(method, args) abort

View File

@ -171,8 +171,8 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
Parameters:~ Parameters:~
{str} String to be converted. {str} String to be converted.
{from_part} Legacy Vim parameter. Usually true. {from_part} Legacy Vim parameter. Usually true.
{do_lt} Also translate <lt>. Does nothing if {do_lt} Also translate <lt>. Ignored if `special` is
`special` is false. false.
{special} Replace |keycodes|, e.g. <CR> becomes a "\n" {special} Replace |keycodes|, e.g. <CR> becomes a "\n"
char. char.
@ -309,20 +309,24 @@ nvim_set_option({name}, {value}) *nvim_set_option()*
{value} New option value {value} New option value
nvim_out_write({str}) *nvim_out_write()* nvim_out_write({str}) *nvim_out_write()*
Writes a message to vim output buffer Writes a message to the Vim output buffer. Does not append
"\n", the message is buffered (won't display) until a linefeed
is written.
Parameters:~ Parameters:~
{str} Message {str} Message
nvim_err_write({str}) *nvim_err_write()* nvim_err_write({str}) *nvim_err_write()*
Writes a message to vim error buffer Writes a message to the Vim error buffer. Does not append
"\n", the message is buffered (won't display) until a linefeed
is written.
Parameters:~ Parameters:~
{str} Message {str} Message
nvim_err_writeln({str}) *nvim_err_writeln()* nvim_err_writeln({str}) *nvim_err_writeln()*
Writes a message to vim error buffer. Appends a linefeed to Writes a message to the Vim error buffer. Appends "\n", so the
ensure all contents are written. buffer is flushed (and displayed).
Parameters:~ Parameters:~
{str} Message {str} Message

View File

@ -1522,14 +1522,16 @@ v:errors Errors found by assert functions, such as |assert_true()|.
*v:event* *event-variable* *v:event* *event-variable*
v:event Dictionary of event data for the current |autocommand|. Valid v:event Dictionary of event data for the current |autocommand|. Valid
only during the autocommand lifetime: storing or passing only during the event lifetime; storing or passing v:event is
`v:event` is invalid. Copy it instead: > invalid! Copy it instead: >
au TextYankPost * let g:foo = deepcopy(v:event) au TextYankPost * let g:foo = deepcopy(v:event)
< Keys vary by event; see the documentation for the specific < Keys vary by event; see the documentation for the specific
event, e.g. |TextYankPost|. event, e.g. |DirChanged| or |TextYankPost|.
KEY DESCRIPTION ~ KEY DESCRIPTION ~
operator The current |operator|. Also set for cwd Current working directory
Ex commands (unlike |v:operator|). For scope Event-specific scope name.
operator Current |operator|. Also set for Ex
commands (unlike |v:operator|). For
example if |TextYankPost| is triggered example if |TextYankPost| is triggered
by the |:yank| Ex command then by the |:yank| Ex command then
`v:event['operator']` is "y". `v:event['operator']` is "y".
@ -4726,7 +4728,8 @@ input({opts})
"-complete=" argument. Refer to |:command-completion| for "-complete=" argument. Refer to |:command-completion| for
more information. Example: > more information. Example: >
let fname = input("File: ", "", "file") let fname = input("File: ", "", "file")
< *E5400* *E5402*
< *input()-highlight* *E5400* *E5402*
The optional `highlight` key allows specifying function which The optional `highlight` key allows specifying function which
will be used for highlighting user input. This function will be used for highlighting user input. This function
receives user input as its only argument and must return receives user input as its only argument and must return
@ -4744,6 +4747,30 @@ input({opts})
sections must be ordered so that next hl_start_col is greater sections must be ordered so that next hl_start_col is greater
then or equal to previous hl_end_col. then or equal to previous hl_end_col.
Example (try some input with parentheses): >
highlight RBP1 guibg=Red ctermbg=red
highlight RBP2 guibg=Yellow ctermbg=yellow
highlight RBP3 guibg=Green ctermbg=green
highlight RBP4 guibg=Blue ctermbg=blue
let g:rainbow_levels = 4
function! RainbowParens(cmdline)
let ret = []
let i = 0
let lvl = 0
while i < len(a:cmdline)
if a:cmdline[i] is# '('
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)])
let lvl += 1
elseif a:cmdline[i] is# ')'
let lvl -= 1
call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)])
endif
let i += 1
endwhile
return ret
endfunction
call input({'prompt':'>','highlight':'RainbowParens'})
<
Highlight function is called at least once for each new Highlight function is called at least once for each new
displayed input string, before command-line is redrawn. It is displayed input string, before command-line is redrawn. It is
expected that function is pure for the duration of one input() expected that function is pure for the duration of one input()

View File

@ -6,9 +6,8 @@
Differences between Nvim and Vim *vim-differences* Differences between Nvim and Vim *vim-differences*
Throughout the help files, differences between Nvim and Vim are indicated via Nvim differs from Vim in many ways, big and small. This document is
the "{Nvim}" tag. This document is a complete and centralized list of all a complete and centralized reference of those differences.
these differences.
Type <M-]> to see the table of contents. Type <M-]> to see the table of contents.
@ -72,12 +71,18 @@ Clipboard integration |provider-clipboard|
USER EXPERIENCE ~ USER EXPERIENCE ~
Working intuitively and consistently is a major goal of Nvim. Examples: Working intuitively and consistently is a major goal of Nvim.
- Nvim does not have `-X`, a platform-specific option "sometimes" available in *feature-compile*
Vim (with potential surprises: http://stackoverflow.com/q/14635295). Nvim - Nvim always includes ALL features, in contrast to Vim (which ships with
avoids features that cannot be provided on all platforms--instead that is various combinations of 100+ optional features). Think of it as a leaner
delegated to external plugins/extensions. version of Vim's "HUGE" build. This reduces surface area for bugs, and
removes a common source of confusion and friction for users.
- Nvim avoids features that cannot be provided on all platforms; instead that
is delegated to external plugins/extensions. E.g. the `-X` platform-specific
option is "sometimes" available in Vim (with potential surprises:
http://stackoverflow.com/q/14635295).
- Vim's internal test functions (test_autochdir(), test_settime(), etc.) are - Vim's internal test functions (test_autochdir(), test_settime(), etc.) are
not exposed (nor implemented); instead Nvim has a robust API. not exposed (nor implemented); instead Nvim has a robust API.
@ -268,13 +273,12 @@ Lua interface (|if_lua.txt|):
- Lua has direct access to Nvim |API| via `vim.api`. - Lua has direct access to Nvim |API| via `vim.api`.
- Lua package.path and package.cpath are automatically updated according to - Lua package.path and package.cpath are automatically updated according to
'runtimepath': |lua-require|. 'runtimepath': |lua-require|.
- Currently, most legacy Vim features are missing.
|input()| and |inputdialog()| gained support for each others features (return |input()| and |inputdialog()| support for each others features (return on
on cancel and completion respectively) via dictionary argument (replaces all cancel and completion respectively) via dictionary argument (replaces all
other arguments if used). other arguments if used).
|input()| and |inputdialog()| now support user-defined cmdline highlighting. |input()| and |inputdialog()| support user-defined cmdline highlighting.
============================================================================== ==============================================================================
5. Missing legacy features *nvim-features-missing* 5. Missing legacy features *nvim-features-missing*
@ -282,7 +286,7 @@ other arguments if used).
Some legacy Vim features are not implemented: Some legacy Vim features are not implemented:
- |if_py|: vim.bindeval() and vim.Function() are not supported - |if_py|: vim.bindeval() and vim.Function() are not supported
- |if_lua|: the `vim` object currently only supports `vim.api` - |if_lua|: the `vim` object is missing most legacy methods
- *if_perl* - *if_perl*
- *if_mzscheme* - *if_mzscheme*
- *if_tcl* - *if_tcl*
@ -290,7 +294,7 @@ Some legacy Vim features are not implemented:
============================================================================== ==============================================================================
6. Removed features *nvim-features-removed* 6. Removed features *nvim-features-removed*
These features are in Vim, but have been intentionally removed from Nvim. These Vim features were intentionally removed from Nvim.
*'cp'* *'nocompatible'* *'nocp'* *'compatible'* *'cp'* *'nocompatible'* *'nocp'* *'compatible'*
Nvim is always "non-compatible" with Vi. Nvim is always "non-compatible" with Vi.

View File

@ -484,7 +484,8 @@ void nvim_set_option(String name, Object value, Error *err)
set_option_to(NULL, SREQ_GLOBAL, name, value, err); set_option_to(NULL, SREQ_GLOBAL, name, value, err);
} }
/// Writes a message to vim output buffer /// Writes a message to the Vim output buffer. Does not append "\n", the
/// message is buffered (won't display) until a linefeed is written.
/// ///
/// @param str Message /// @param str Message
void nvim_out_write(String str) void nvim_out_write(String str)
@ -493,7 +494,8 @@ void nvim_out_write(String str)
write_msg(str, false); write_msg(str, false);
} }
/// Writes a message to vim error buffer /// Writes a message to the Vim error buffer. Does not append "\n", the
/// message is buffered (won't display) until a linefeed is written.
/// ///
/// @param str Message /// @param str Message
void nvim_err_write(String str) void nvim_err_write(String str)
@ -502,8 +504,8 @@ void nvim_err_write(String str)
write_msg(str, true); write_msg(str, true);
} }
/// Writes a message to vim error buffer. Appends a linefeed to ensure all /// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
/// contents are written. /// flushed (and displayed).
/// ///
/// @param str Message /// @param str Message
/// @see nvim_err_write() /// @see nvim_err_write()

View File

@ -22775,7 +22775,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
bool eval_has_provider(const char *name) bool eval_has_provider(const char *name)
{ {
#define check_provider(name) \ #define CHECK_PROVIDER(name) \
if (has_##name == -1) { \ if (has_##name == -1) { \
has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \
if (!has_##name) { \ if (!has_##name) { \
@ -22791,17 +22791,17 @@ bool eval_has_provider(const char *name)
static int has_python3 = -1; static int has_python3 = -1;
static int has_ruby = -1; static int has_ruby = -1;
if (!strcmp(name, "clipboard")) { if (strequal(name, "clipboard")) {
check_provider(clipboard); CHECK_PROVIDER(clipboard);
return has_clipboard; return has_clipboard;
} else if (!strcmp(name, "python3")) { } else if (strequal(name, "python3")) {
check_provider(python3); CHECK_PROVIDER(python3);
return has_python3; return has_python3;
} else if (!strcmp(name, "python")) { } else if (strequal(name, "python")) {
check_provider(python); CHECK_PROVIDER(python);
return has_python; return has_python;
} else if (!strcmp(name, "ruby")) { } else if (strequal(name, "ruby")) {
check_provider(ruby); CHECK_PROVIDER(ruby);
return has_ruby; return has_ruby;
} }

View File

@ -1878,54 +1878,47 @@ static void usage(void)
signal_stop(); // kill us with CTRL-C here, if you like signal_stop(); // kill us with CTRL-C here, if you like
mch_msg(_("Usage:\n")); mch_msg(_("Usage:\n"));
mch_msg(_(" nvim [arguments] [file ...] Edit specified file(s)\n")); mch_msg(_(" nvim [options] [file ...] Edit file(s)\n"));
mch_msg(_(" nvim [arguments] - Read text from stdin\n")); mch_msg(_(" nvim [options] - Read text from stdin\n"));
mch_msg(_(" nvim [arguments] -t <tag> Edit file where tag is defined\n")); mch_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n"));
mch_msg(_(" nvim [arguments] -q [errorfile] Edit file with first error\n")); mch_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n"));
mch_msg(_("\nArguments:\n")); mch_msg(_("\nOptions:\n"));
mch_msg(_(" -- Only file names after this\n")); mch_msg(_(" -- Only file names after this\n"));
mch_msg(_(" + Start at end of file\n"));
mch_msg(_(" --cmd <cmd> Execute <cmd> before any config\n"));
mch_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"));
mch_msg("\n");
mch_msg(_(" -b Binary mode\n"));
mch_msg(_(" -d Diff mode\n"));
mch_msg(_(" -e, -E Ex mode, Improved Ex mode\n"));
mch_msg(_(" -es Silent (batch) mode\n"));
mch_msg(_(" -h, --help Print this help message\n"));
mch_msg(_(" -i <shada> Use this shada file\n"));
mch_msg(_(" -m Modifications (writing files) not allowed\n"));
mch_msg(_(" -M Modifications in text not allowed\n"));
mch_msg(_(" -n No swap file, use memory only\n"));
mch_msg(_(" -o[N] Open N windows (default: one per file)\n"));
mch_msg(_(" -O[N] Open N vertical windows (default: one per file)\n"));
mch_msg(_(" -p[N] Open N tab pages (default: one per file)\n"));
mch_msg(_(" -r, -L List swap files\n"));
mch_msg(_(" -r <file> Recover edit state for this file\n"));
mch_msg(_(" -R Read-only mode\n"));
mch_msg(_(" -S <session> Source <session> after loading the first file\n"));
mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
mch_msg(_(" -u <config> Use this config file\n"));
mch_msg(_(" -v, --version Print version information\n"));
mch_msg(_(" -V[N][file] Verbose [level][file]\n"));
mch_msg(_(" -Z Restricted mode\n"));
mch_msg("\n");
mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n"));
mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n"));
mch_msg(_(" --headless Don't start a user interface\n"));
#if !defined(UNIX) #if !defined(UNIX)
mch_msg(_(" --literal Don't expand wildcards\n")); mch_msg(_(" --literal Don't expand wildcards\n"));
#endif #endif
mch_msg(_(" -e Ex mode\n")); mch_msg(_(" --noplugin Don't load plugins\n"));
mch_msg(_(" -E Improved Ex mode\n"));
mch_msg(_(" -s Silent (batch) mode (only for ex mode)\n"));
mch_msg(_(" -d Diff mode\n"));
mch_msg(_(" -R Read-only mode\n"));
mch_msg(_(" -Z Restricted mode\n"));
mch_msg(_(" -m Modifications (writing files) not allowed\n"));
mch_msg(_(" -M Modifications in text not allowed\n"));
mch_msg(_(" -b Binary mode\n"));
mch_msg(_(" -l Lisp mode\n"));
mch_msg(_(" -A Arabic mode\n"));
mch_msg(_(" -F Farsi mode\n"));
mch_msg(_(" -H Hebrew mode\n"));
mch_msg(_(" -V[N][file] Be verbose [level N][log messages to file]\n"));
mch_msg(_(" -D Debugging mode\n"));
mch_msg(_(" -n No swap file, use memory only\n"));
mch_msg(_(" -r, -L List swap files and exit\n"));
mch_msg(_(" -r <file> Recover crashed session\n"));
mch_msg(_(" -u <vimrc> Use <vimrc> instead of the default\n"));
mch_msg(_(" -i <shada> Use <shada> instead of the default\n"));
mch_msg(_(" --noplugin Don't load plugin scripts\n"));
mch_msg(_(" -o[N] Open N windows (default: one for each file)\n"));
mch_msg(_(" -O[N] Like -o but split vertically\n"));
mch_msg(_(" -p[N] Open N tab pages (default: one for each file)\n"));
mch_msg(_(" + Start at end of file\n"));
mch_msg(_(" +<linenum> Start at line <linenum>\n"));
mch_msg(_(" +/<pattern> Start at first occurrence of <pattern>\n"));
mch_msg(_(" --cmd <command> Execute <command> before loading any vimrc\n"));
mch_msg(_(" -c <command> Execute <command> after loading the first file\n"));
mch_msg(_(" -S <session> Source <session> after loading the first file\n"));
mch_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
mch_msg(_(" -w <scriptout> Append all typed characters to <scriptout>\n"));
mch_msg(_(" -W <scriptout> Write all typed characters to <scriptout>\n"));
mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n"));
mch_msg(_(" --api-info Dump API metadata serialized to msgpack and exit\n")); mch_msg(_("\nSee \":help startup-options\" for all options.\n"));
mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n"));
mch_msg(_(" --headless Don't start a user interface\n"));
mch_msg(_(" -v, --version Print version information and exit\n"));
mch_msg(_(" -h, --help Print this help message and exit\n"));
} }

View File

@ -58,8 +58,8 @@ static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
static bool clipboard_didwarn_unnamed = false; static bool clipboard_didwarn_unnamed = false;
// for behavior between start_batch_changes() and end_batch_changes()) // for behavior between start_batch_changes() and end_batch_changes())
static bool clipboard_delay_update = false; // delay clipboard update
static int batch_change_count = 0; // inside a script static int batch_change_count = 0; // inside a script
static bool clipboard_delay_update = false; // delay clipboard update
static bool clipboard_needs_update = false; // clipboard was updated static bool clipboard_needs_update = false; // clipboard was updated
/* /*
@ -5524,7 +5524,7 @@ int get_default_register_name(void)
} }
/// Determine if register `*name` should be used as a clipboard. /// Determine if register `*name` should be used as a clipboard.
/// In an unnammed operation, `*name` is `NUL` and will be adjusted to `'*'/'+'` if /// In an unnamed operation, `*name` is `NUL` and will be adjusted to */+ if
/// `clipboard=unnamed[plus]` is set. /// `clipboard=unnamed[plus]` is set.
/// ///
/// @param name The name of register, or `NUL` if unnamed. /// @param name The name of register, or `NUL` if unnamed.
@ -5535,33 +5535,46 @@ int get_default_register_name(void)
/// if the register isn't a clipboard or provider isn't available. /// if the register isn't a clipboard or provider isn't available.
static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing) static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
{ {
if (*name == '*' || *name == '+') { #define MSG_NO_CLIP "clipboard: No provider. " \
if(!eval_has_provider("clipboard")) { "Try \":CheckHealth\" or \":h clipboard\"."
if (!quiet) {
EMSG("clipboard: No provider. Try \":CheckHealth\" or " yankreg_T *target = NULL;
"\":h clipboard\"."); bool explicit_cb_reg = (*name == '*' || *name == '+');
} bool implicit_cb_reg = (*name == NUL) && (cb_flags & CB_UNNAMEDMASK);
return NULL; int save_redir_off = redir_off;
} if (!explicit_cb_reg && !implicit_cb_reg) {
return &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER]; goto end;
} else if ((*name == NUL) && (cb_flags & CB_UNNAMEDMASK)) { }
if(!eval_has_provider("clipboard")) {
if (!quiet && !clipboard_didwarn_unnamed) { if (!eval_has_provider("clipboard")) {
msg((char_u *)"clipboard: No provider. Try \":CheckHealth\" or " if (batch_change_count == 1 && explicit_cb_reg && !quiet) {
"\":h clipboard\"."); redir_off = true; // Avoid recursion from :redir + emsg().
clipboard_didwarn_unnamed = true; // Do NOT error (emsg()) here--if it interrupts :redir we get into
} // a weird state, stuck in "redirect mode".
return NULL; msg((char_u *)MSG_NO_CLIP);
} else if (batch_change_count == 1 && implicit_cb_reg
&& !quiet && !clipboard_didwarn_unnamed) {
redir_off = true; // Avoid recursion from :redir + emsg().
msg((char_u *)MSG_NO_CLIP);
clipboard_didwarn_unnamed = true;
} }
// ... else, be silent (don't flood during :while, :redir, etc.).
goto end;
}
if (explicit_cb_reg) {
target = &y_regs[*name == '*' ? STAR_REGISTER : PLUS_REGISTER];
goto end;
} else { // unnamed register: "implicit" clipboard
if (writing && clipboard_delay_update) { if (writing && clipboard_delay_update) {
// For "set" (copy), defer the clipboard call.
clipboard_needs_update = true; clipboard_needs_update = true;
return NULL; goto end;
} else if (!writing && clipboard_needs_update) { } else if (!writing && clipboard_needs_update) {
// use the internal value // For "get" (paste), use the internal value.
return NULL; goto end;
} }
yankreg_T *target;
if (cb_flags & CB_UNNAMEDPLUS) { if (cb_flags & CB_UNNAMEDPLUS) {
*name = (cb_flags & CB_UNNAMED && writing) ? '"': '+'; *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+';
target = &y_regs[PLUS_REGISTER]; target = &y_regs[PLUS_REGISTER];
@ -5569,10 +5582,12 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
*name = '*'; *name = '*';
target = &y_regs[STAR_REGISTER]; target = &y_regs[STAR_REGISTER];
} }
return target; // unnamed register goto end;
} }
// don't do anything for other register names
return NULL; end:
redir_off = save_redir_off;
return target;
} }
static bool get_clipboard(int name, yankreg_T **target, bool quiet) static bool get_clipboard(int name, yankreg_T **target, bool quiet)
@ -5740,7 +5755,7 @@ static void set_clipboard(int name, yankreg_T *reg)
(void)eval_call_provider("clipboard", "set", args); (void)eval_call_provider("clipboard", "set", args);
} }
/// Avoid clipboard (slow) during batch operations (i.e., a script). /// Avoid slow things (clipboard) during batch operations (while/for-loops).
void start_batch_changes(void) void start_batch_changes(void)
{ {
if (++batch_change_count > 1) { if (++batch_change_count > 1) {
@ -5750,7 +5765,7 @@ void start_batch_changes(void)
clipboard_needs_update = false; clipboard_needs_update = false;
} }
/// Update the clipboard after batch changes finished. /// Counterpart to start_batch_changes().
void end_batch_changes(void) void end_batch_changes(void)
{ {
if (--batch_change_count > 0) { if (--batch_change_count > 0) {
@ -5759,6 +5774,7 @@ void end_batch_changes(void)
} }
clipboard_delay_update = false; clipboard_delay_update = false;
if (clipboard_needs_update) { if (clipboard_needs_update) {
// unnamed ("implicit" clipboard)
set_clipboard(NUL, y_previous); set_clipboard(NUL, y_previous);
clipboard_needs_update = false; clipboard_needs_update = false;
} }

View File

@ -76,11 +76,11 @@ func Test_help_arg()
let found = [] let found = []
for line in lines for line in lines
if line =~ '-R.*Read-only mode' if line =~ '-R.*Read-only mode'
call add(found, 'Readonly mode') call add(found, 'Readonly mode')
endif endif
" Watch out for a second --version line in the Gnome version. " Watch out for a second --version line in the Gnome version.
if line =~ '--version.*Print version information and exit' if line =~ '--version.*Print version information'
call add(found, "--version") call add(found, "--version")
endif endif
endfor endfor
call assert_equal(['Readonly mode', '--version'], found) call assert_equal(['Readonly mode', '--version'], found)

View File

@ -1089,13 +1089,7 @@ static void list_features(void)
msg_putchar('\n'); msg_putchar('\n');
} }
} else { } else {
while (msg_col % width) { msg_putchar(' ');
int old_msg_col = msg_col;
msg_putchar(' ');
if (old_msg_col == msg_col) {
break; // XXX: Avoid infinite loop.
}
}
} }
} else { } else {
if (msg_col > 0) { if (msg_col > 0) {
@ -1103,7 +1097,7 @@ static void list_features(void)
} }
} }
} }
MSG_PUTS("For differences from Vim, see :help vim-differences\n\n"); MSG_PUTS("See \":help feature-compile\"\n\n");
} }
void list_version(void) void list_version(void)
@ -1144,7 +1138,7 @@ void list_version(void)
} }
#endif // ifdef HAVE_PATHDEF #endif // ifdef HAVE_PATHDEF
version_msg(_("\n\nOptional features included (+) or not (-): ")); version_msg(_("\n\nFeatures: "));
list_features(); list_features();
@ -1216,7 +1210,6 @@ void intro_message(int colon)
static char *(lines[]) = { static char *(lines[]) = {
N_(NVIM_VERSION_LONG), N_(NVIM_VERSION_LONG),
"", "",
N_("by al."),
N_("Nvim is open source and freely distributable"), N_("Nvim is open source and freely distributable"),
N_("https://neovim.io/community"), N_("https://neovim.io/community"),
"", "",

View File

@ -4,6 +4,8 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen') local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local feed_command, expect, eq, eval = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval local feed_command, expect, eq, eval = helpers.feed_command, helpers.expect, helpers.eq, helpers.eval
local command = helpers.command
local meths = helpers.meths
local function basic_register_test(noblock) local function basic_register_test(noblock)
insert("some words") insert("some words")
@ -80,15 +82,47 @@ local function basic_register_test(noblock)
expect("two and three and one") expect("two and three and one")
end end
describe('the unnamed register', function() describe('clipboard', function()
before_each(clear) before_each(clear)
it('works without provider', function()
it('unnamed register works without provider', function()
eq('"', eval('v:register')) eq('"', eval('v:register'))
basic_register_test() basic_register_test()
end) end)
it('`:redir @+>` with invalid g:clipboard shows error exactly once', function()
local screen = Screen.new(72, 5)
screen:attach()
command("let g:clipboard = 'bogus'")
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
screen:expect([[
^ |
~ |
~ |
~ |
clipboard: No provider. Try ":CheckHealth" or ":h clipboard". |
]], nil, {{bold = true, foreground = Screen.colors.Blue}})
end)
it('invalid g:clipboard', function()
command("let g:clipboard = 'bogus'")
eq('', eval('provider#clipboard#Executable()'))
eq('clipboard: invalid g:clipboard', eval('provider#clipboard#Error()'))
end)
it('valid g:clipboard', function()
-- provider#clipboard#Executable() only checks the structure.
meths.set_var('clipboard', {
['name'] = 'clippy!',
['copy'] = { ['+'] = 'any command', ['*'] = 'some other' },
['paste'] = { ['+'] = 'any command', ['*'] = 'some other' },
})
eq('clippy!', eval('provider#clipboard#Executable()'))
eq('', eval('provider#clipboard#Error()'))
end)
end) end)
describe('clipboard usage', function() describe('clipboard', function()
local function reset(...) local function reset(...)
clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...) clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...)
end end
@ -98,7 +132,23 @@ describe('clipboard usage', function()
feed_command('call getreg("*")') -- force load of provider feed_command('call getreg("*")') -- force load of provider
end) end)
it('has independent "* and unnamed registers per default', function() it('`:redir @+>` invokes clipboard once-per-message', function()
eq(0, eval("g:clip_called_set"))
feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END')
-- Assuming CONTRIBUTING.md has >100 lines.
assert(eval("g:clip_called_set") > 100)
end)
it('`:redir @">` does NOT invoke clipboard', function()
-- :redir to a non-clipboard register, with `:set clipboard=unnamed` does
-- NOT propagate to the clipboard. This is consistent with Vim.
command("set clipboard=unnamedplus")
eq(0, eval("g:clip_called_set"))
feed_command('redir @"> | :silent echo system("cat CONTRIBUTING.md") | redir END')
eq(0, eval("g:clip_called_set"))
end)
it('has independent "* and unnamed registers by default', function()
insert("some words") insert("some words")
feed('^"*dwdw"*P') feed('^"*dwdw"*P')
expect('some ') expect('some ')
@ -139,7 +189,7 @@ describe('clipboard usage', function()
eq({'some\ntext', '\nvery binary\n'}, eval("getreg('*', 1, 1)")) eq({'some\ntext', '\nvery binary\n'}, eval("getreg('*', 1, 1)"))
end) end)
it('support autodectection of regtype', function() it('autodetects regtype', function()
feed_command("let g:test_clip['*'] = ['linewise stuff','']") feed_command("let g:test_clip['*'] = ['linewise stuff','']")
feed_command("let g:test_clip['+'] = ['charwise','stuff']") feed_command("let g:test_clip['+'] = ['charwise','stuff']")
eq("V", eval("getregtype('*')")) eq("V", eval("getregtype('*')"))
@ -169,7 +219,7 @@ describe('clipboard usage', function()
eq({{' much', 'ktext', ''}, 'b'}, eval("g:test_clip['+']")) eq({{' much', 'ktext', ''}, 'b'}, eval("g:test_clip['+']"))
end) end)
it('supports setreg', function() it('supports setreg()', function()
feed_command('call setreg("*", "setted\\ntext", "c")') feed_command('call setreg("*", "setted\\ntext", "c")')
feed_command('call setreg("+", "explicitly\\nlines", "l")') feed_command('call setreg("+", "explicitly\\nlines", "l")')
feed('"+P"*p') feed('"+P"*p')
@ -187,7 +237,7 @@ describe('clipboard usage', function()
]]) ]])
end) end)
it('supports let @+ (issue #1427)', function() it('supports :let @+ (issue #1427)', function()
feed_command("let @+ = 'some'") feed_command("let @+ = 'some'")
feed_command("let @* = ' other stuff'") feed_command("let @* = ' other stuff'")
eq({{'some'}, 'v'}, eval("g:test_clip['+']")) eq({{'some'}, 'v'}, eval("g:test_clip['+']"))
@ -305,7 +355,7 @@ describe('clipboard usage', function()
end) end)
describe('with clipboard=unnamedplus', function() describe('clipboard=unnamedplus', function()
before_each(function() before_each(function()
feed_command('set clipboard=unnamedplus') feed_command('set clipboard=unnamedplus')
end) end)
@ -349,6 +399,7 @@ describe('clipboard usage', function()
really unnamed really unnamed
the plus]]) the plus]])
end) end)
it('is updated on global changes', function() it('is updated on global changes', function()
insert([[ insert([[
text text

View File

@ -5,7 +5,13 @@ let s:methods = {}
let g:cliplossy = 0 let g:cliplossy = 0
let g:cliperror = 0 let g:cliperror = 0
" Count how many times the clipboard was invoked.
let g:clip_called_get = 0
let g:clip_called_set = 0
function! s:methods.get(reg) function! s:methods.get(reg)
let g:clip_called_get += 1
if g:cliperror if g:cliperror
return 0 return 0
end end
@ -19,6 +25,8 @@ function! s:methods.get(reg)
endfunction endfunction
function! s:methods.set(lines, regtype, reg) function! s:methods.set(lines, regtype, reg)
let g:clip_called_set += 1
if a:reg == '"' if a:reg == '"'
call s:methods.set(a:lines,a:regtype,'+') call s:methods.set(a:lines,a:regtype,'+')
call s:methods.set(a:lines,a:regtype,'*') call s:methods.set(a:lines,a:regtype,'*')