Merge remote-tracking branch 'upstream/master'

This commit is contained in:
ckelsel 2017-07-28 08:38:08 +08:00
commit 3e0536eb29
38 changed files with 652 additions and 193 deletions

3
.gitignore vendored
View File

@ -17,6 +17,9 @@ tags
/src/nvim/po/vim.pot /src/nvim/po/vim.pot
/src/nvim/po/*.ck /src/nvim/po/*.ck
# generated by tests with $NVIM_LOG_FILE set.
/.nvimlog
# Files generated by scripts/vim-patch.sh # Files generated by scripts/vim-patch.sh
/.vim-src/ /.vim-src/

View File

@ -240,6 +240,14 @@ if(HAS_DIAG_COLOR_FLAG)
add_definitions(-fdiagnostics-color=auto) add_definitions(-fdiagnostics-color=auto)
endif() endif()
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "4.8.5")
# Array-bounds testing is broken in some GCC versions before 4.8.5.
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56273
add_definitions(-Wno-array-bounds)
endif()
endif()
option(TRAVIS_CI_BUILD "Travis/QuickBuild CI. Extra flags will be set." OFF) option(TRAVIS_CI_BUILD "Travis/QuickBuild CI. Extra flags will be set." OFF)
if(TRAVIS_CI_BUILD) if(TRAVIS_CI_BUILD)

View File

@ -100,20 +100,6 @@ add_custom_target(
${GENERATED_PACKAGE_TAGS} ${GENERATED_PACKAGE_TAGS}
) )
# Optional targets for nvim.desktop file and icon.
find_program(XDG_MENU_PRG xdg-desktop-menu)
find_program(XDG_ICON_PRG xdg-icon-resource)
if(XDG_MENU_PRG)
add_custom_target(desktop-file
COMMAND xdg-desktop-menu install --novendor ${PROJECT_SOURCE_DIR}/runtime/nvim.desktop)
# add_dependencies(runtime desktop-file)
endif()
if(XDG_ICON_PRG)
add_custom_target(desktop-icon
COMMAND xdg-icon-resource install --novendor --size 128 ${PROJECT_SOURCE_DIR}/runtime/nvim.png)
# add_dependencies(runtime desktop-icon)
endif()
# CMake is painful here. It will create the destination using the user's # CMake is painful here. It will create the destination using the user's
# current umask, and we don't want that. And we don't just want to install # current umask, and we don't want that. And we don't just want to install
# the target directory, as it will mess with existing permissions. So this # the target directory, as it will mess with existing permissions. So this
@ -128,6 +114,16 @@ install_helper(
FILES ${GENERATED_SYN_VIM} FILES ${GENERATED_SYN_VIM}
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/syntax/vim) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/syntax/vim)
if(NOT APPLE)
install_helper(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.desktop
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
install_helper(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pixmaps)
endif()
file(GLOB_RECURSE RUNTIME_PROGRAMS file(GLOB_RECURSE RUNTIME_PROGRAMS
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
*.awk *.sh *.bat) *.awk *.sh *.bat)

View File

@ -0,0 +1,18 @@
" Common functionality for providers
let s:stderr = {}
function! provider#stderr_collector(chan_id, data, event) dict
let stderr = get(s:stderr, a:chan_id, [''])
let stderr[-1] .= a:data[0]
call extend(stderr, a:data[1:])
let s:stderr[a:chan_id] = stderr
endfunction
function! provider#clear_stderr(chan_id)
silent! call delete(s:stderr, a:chan_id)
endfunction
function! provider#get_stderr(chan_id)
return get(s:stderr, a:chan_id, [])
endfunction

View File

@ -6,7 +6,7 @@ let s:paste = {}
" 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.
let s:selection = { 'owner': 0, 'data': [] } let s:selection = { 'owner': 0, 'data': [], 'on_stderr': function('provider#stderr_collector') }
function! s:selection.on_exit(jobid, data, event) abort function! s:selection.on_exit(jobid, data, event) abort
" At this point this nvim instance might already have launched " At this point this nvim instance might already have launched
@ -14,12 +14,13 @@ function! s:selection.on_exit(jobid, data, event) abort
if self.owner == a:jobid if self.owner == a:jobid
let self.owner = 0 let self.owner = 0
endif endif
endfunction if a:data != 0
let stderr = provider#get_stderr(a:jobid)
function! s:selection.on_stderr(jobid, data, event) abort echohl WarningMsg
echohl WarningMsg echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(stderr)
echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(a:data) echohl None
echohl None endif
call provider#clear_stderr(a:jobid)
endfunction endfunction
let s:selections = { '*': s:selection, '+': copy(s:selection)} let s:selections = { '*': s:selection, '+': copy(s:selection)}

View File

@ -5,17 +5,7 @@ endif
let s:loaded_pythonx_provider = 1 let s:loaded_pythonx_provider = 1
let s:stderr = {} let s:job_opts = {'rpc': v:true, 'on_stderr': function('provider#stderr_collector')}
let s:job_opts = {'rpc': v:true}
" TODO(bfredl): this logic is common and should be builtin
function! s:job_opts.on_stderr(chan_id, data, event)
let stderr = get(s:stderr, a:chan_id, [''])
let last = remove(stderr, -1)
let a:data[0] = last.a:data[0]
call extend(stderr, a:data)
let s:stderr[a:chan_id] = stderr
endfunction
function! provider#pythonx#Require(host) abort function! provider#pythonx#Require(host) abort
let ver = (a:host.orig_name ==# 'python') ? 2 : 3 let ver = (a:host.orig_name ==# 'python') ? 2 : 3
@ -38,9 +28,11 @@ function! provider#pythonx#Require(host) abort
catch catch
echomsg v:throwpoint echomsg v:throwpoint
echomsg v:exception echomsg v:exception
for row in get(s:stderr, channel_id, []) for row in provider#get_stderr(channel_id)
echomsg row echomsg row
endfor endfor
finally
call provider#clear_stderr(channel_id)
endtry endtry
throw remote#host#LoadErrorForHost(a:host.orig_name, throw remote#host#LoadErrorForHost(a:host.orig_name,
\ '$NVIM_PYTHON_LOG_FILE') \ '$NVIM_PYTHON_LOG_FILE')

View File

@ -143,7 +143,7 @@ a standard meaning:
Two 2 Hook Two 2 Hook
Nine 9 Horn Nine 9 Horn
Equals = Cyrillic (= used as second char) Equals = Cyrillic (= used as second char)
Asterisk * Greek Asterisk * Greek
Percent sign % Greek/Cyrillic special Percent sign % Greek/Cyrillic special
Plus + smalls: Arabic, capitals: Hebrew Plus + smalls: Arabic, capitals: Hebrew
@ -922,6 +922,7 @@ char digraph hex dec official name ~
† /- 2020 8224 DAGGER † /- 2020 8224 DAGGER
‡ /= 2021 8225 DOUBLE DAGGER ‡ /= 2021 8225 DOUBLE DAGGER
‥ .. 2025 8229 TWO DOT LEADER ‥ .. 2025 8229 TWO DOT LEADER
… ,. 2026 8230 HORIZONTAL ELLIPSIS
‰ %0 2030 8240 PER MILLE SIGN ‰ %0 2030 8240 PER MILLE SIGN
1' 2032 8242 PRIME 1' 2032 8242 PRIME
″ 2' 2033 8243 DOUBLE PRIME ″ 2' 2033 8243 DOUBLE PRIME

View File

@ -4046,7 +4046,9 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
locale locale names (as output of locale -a) locale locale names (as output of locale -a)
mapping mapping name mapping mapping name
menu menus menu menus
messages |:messages| suboptions
option options option options
packadd optional package |pack-add| names
shellcmd Shell command shellcmd Shell command
sign |:sign| suboptions sign |:sign| suboptions
syntax syntax file names |'syntax'| syntax syntax file names |'syntax'|

View File

@ -1202,6 +1202,7 @@ completion can be enabled:
-complete=locale locale names (as output of locale -a) -complete=locale locale names (as output of locale -a)
-complete=mapping mapping name -complete=mapping mapping name
-complete=menu menus -complete=menu menus
-complete=messages |:messages| suboptions
-complete=option options -complete=option options
-complete=packadd optional package |pack-add| names -complete=packadd optional package |pack-add| names
-complete=shellcmd Shell command -complete=shellcmd Shell command

View File

@ -1,14 +1,26 @@
## Source code overview Nvim core source
================
This document is an overview of how Nvim works internally, focusing on parts Module-specific details are documented at the top of each module (`terminal.c`,
that are different from Vim. Since Nvim inherited from Vim, some information in `screen.c`, ...).
[its README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt)
still applies.
For module-specific details, read the source code. Some files are extensively See `:help development` for more guidelines.
commented at the top (e.g. terminal.c, screen.c).
### Source file name conventions Logs
----
Low-level log messages sink to `$NVIM_LOG_FILE`.
You can use `LOG_CALLSTACK()` anywhere in the source to log the current
stacktrace. (Currently Linux-only.)
UI events are logged at level 0 (`DEBUG_LOG_LEVEL`).
rm -rf build/
make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
Filename conventions
--------------------
The source files use extensions to hint about their purpose. The source files use extensions to hint about their purpose.
@ -19,10 +31,12 @@ The source files use extensions to hint about their purpose.
- `*.h.generated.h` - exported functions declarations. - `*.h.generated.h` - exported functions declarations.
- `*.c.generated.h` - static functions declarations. - `*.c.generated.h` - static functions declarations.
### Top-level program loops Nvim lifecycle
--------------
Let's understand what a Vim-like program does by analyzing the workflow of Following describes how Nvim processes input.
a typical editing session:
Consider a typical Vim-like editing session:
01. Vim dispays the welcome screen 01. Vim dispays the welcome screen
02. User types: `:` 02. User types: `:`
@ -154,7 +168,8 @@ modes managed by the `state_enter` loop:
- insert mode: `insert_{enter,check,execute}()`(`edit.c`) - insert mode: `insert_{enter,check,execute}()`(`edit.c`)
- terminal mode: `terminal_{enter,execute}()`(`terminal.c`) - terminal mode: `terminal_{enter,execute}()`(`terminal.c`)
### Async event support Async event support
-------------------
One of the features Nvim added is the support for handling arbitrary One of the features Nvim added is the support for handling arbitrary
asynchronous events, which can include: asynchronous events, which can include:

View File

@ -1203,8 +1203,8 @@ do_buffer (
*/ */
while (buf == curbuf while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0) && !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (firstwin != lastwin || first_tabpage->tp_next != NULL)) { && (!ONE_WINDOW || first_tabpage->tp_next != NULL)) {
if (win_close(curwin, FALSE) == FAIL) if (win_close(curwin, false) == FAIL)
break; break;
} }
@ -4428,15 +4428,17 @@ do_arg_all (
continue; continue;
} }
} }
/* don't close last window */ // don't close last window
if (firstwin == lastwin if (ONE_WINDOW
&& (first_tabpage->tp_next == NULL || !had_tab)) && (first_tabpage->tp_next == NULL || !had_tab)) {
use_firstwin = TRUE; use_firstwin = true;
else { } else {
win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); win_close(wp, !P_HID(buf) && !bufIsChanged(buf));
/* check if autocommands removed the next window */ // check if autocommands removed the next window
if (!win_valid(wpnext)) if (!win_valid(wpnext)) {
wpnext = firstwin; /* start all over... */ // start all over...
wpnext = firstwin;
}
} }
} }
} }
@ -4593,7 +4595,7 @@ void ex_buffer_all(exarg_T *eap)
- tabline_height() - tabline_height()
: wp->w_width != Columns) : wp->w_width != Columns)
|| (had_tab > 0 && wp != firstwin)) || (had_tab > 0 && wp != firstwin))
&& firstwin != lastwin && !ONE_WINDOW
&& !(wp->w_closing || wp->w_buffer->b_locked > 0) && !(wp->w_closing || wp->w_buffer->b_locked > 0)
) { ) {
win_close(wp, FALSE); win_close(wp, FALSE);

View File

@ -793,6 +793,7 @@ static digr_T digraphdefault[] =
{ '/', '-', 0x2020 }, { '/', '-', 0x2020 },
{ '/', '=', 0x2021 }, { '/', '=', 0x2021 },
{ '.', '.', 0x2025 }, { '.', '.', 0x2025 },
{ ',', '.', 0x2026 },
{ '%', '0', 0x2030 }, { '%', '0', 0x2030 },
{ '1', '\'', 0x2032 }, { '1', '\'', 0x2032 },
{ '2', '\'', 0x2033 }, { '2', '\'', 0x2033 },

View File

@ -15141,7 +15141,8 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr)
} }
const char *error = NULL; const char *error = NULL;
uint64_t id = channel_connect(tcp, address, 50, &error); eval_format_source_name_line((char *)IObuff, sizeof(IObuff));
uint64_t id = channel_connect(tcp, address, 50, (char *)IObuff, &error);
if (error) { if (error) {
EMSG2(_("connection failed: %s"), error); EMSG2(_("connection failed: %s"), error);
@ -22449,8 +22450,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv)
if (data->rpc) { if (data->rpc) {
// the rpc channel takes over the in and out streams eval_format_source_name_line((char *)IObuff, sizeof(IObuff));
channel_from_process(proc, data->id); // RPC channel takes over the in/out streams.
channel_from_process(proc, data->id, (char *)IObuff);
} else { } else {
wstream_init(proc->in, 0); wstream_init(proc->in, 0);
if (proc->out) { if (proc->out) {
@ -22775,3 +22777,11 @@ bool eval_has_provider(const char *name)
return false; return false;
} }
/// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`.
void eval_format_source_name_line(char *buf, size_t bufsize)
{
snprintf(buf, bufsize, "%s:%" PRIdLINENR,
(sourcing_name ? sourcing_name : (char_u *)"?"),
(sourcing_name ? sourcing_lnum : 0));
}

View File

@ -2802,16 +2802,18 @@ void ex_z(exarg_T *eap)
int j; int j;
linenr_T lnum = eap->line2; linenr_T lnum = eap->line2;
/* Vi compatible: ":z!" uses display height, without a count uses // Vi compatible: ":z!" uses display height, without a count uses
* 'scroll' */ // 'scroll'
if (eap->forceit) if (eap->forceit) {
bigness = curwin->w_height; bigness = curwin->w_height;
else if (firstwin == lastwin) } else if (ONE_WINDOW) {
bigness = curwin->w_p_scr * 2; bigness = curwin->w_p_scr * 2;
else } else {
bigness = curwin->w_height - 3; bigness = curwin->w_height - 3;
if (bigness < 1) }
if (bigness < 1) {
bigness = 1; bigness = 1;
}
x = eap->arg; x = eap->arg;
kind = x; kind = x;

View File

@ -844,8 +844,6 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
break; break;
case ET_INTERRUPT: case ET_INTERRUPT:
break; break;
default:
p = vim_strsave((char_u *)_(e_internal));
} }
saved_sourcing_name = sourcing_name; saved_sourcing_name = sourcing_name;
@ -3440,6 +3438,11 @@ const char * set_one_cmd_context(
xp->xp_pattern = (char_u *)arg; xp->xp_pattern = (char_u *)arg;
break; break;
case CMD_messages:
xp->xp_context = EXPAND_MESSAGES;
xp->xp_pattern = (char_u *)arg;
break;
case CMD_history: case CMD_history:
xp->xp_context = EXPAND_HISTORY; xp->xp_context = EXPAND_HISTORY;
xp->xp_pattern = (char_u *)arg; xp->xp_pattern = (char_u *)arg;
@ -4874,6 +4877,7 @@ static struct {
#endif #endif
{ EXPAND_MAPPINGS, "mapping" }, { EXPAND_MAPPINGS, "mapping" },
{ EXPAND_MENUS, "menu" }, { EXPAND_MENUS, "menu" },
{ EXPAND_MESSAGES, "messages" },
{ EXPAND_OWNSYNTAX, "syntax" }, { EXPAND_OWNSYNTAX, "syntax" },
{ EXPAND_SYNTIME, "syntime" }, { EXPAND_SYNTIME, "syntime" },
{ EXPAND_SETTINGS, "option" }, { EXPAND_SETTINGS, "option" },
@ -5976,7 +5980,7 @@ static void ex_quit(exarg_T *eap)
// specified. Example: // specified. Example:
// :h|wincmd w|1q - don't quit // :h|wincmd w|1q - don't quit
// :h|wincmd w|q - quit // :h|wincmd w|q - quit
if (only_one_window() && (firstwin == lastwin || eap->addr_count == 0)) { if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) {
getout(0); getout(0);
} }
/* close window; may free buffer */ /* close window; may free buffer */
@ -6174,12 +6178,14 @@ static void ex_tabonly(exarg_T *eap)
*/ */
void tabpage_close(int forceit) void tabpage_close(int forceit)
{ {
/* First close all the windows but the current one. If that worked then // First close all the windows but the current one. If that worked then
* close the last window in this tab, that will close it. */ // close the last window in this tab, that will close it.
if (lastwin != firstwin) if (!ONE_WINDOW) {
close_others(TRUE, forceit); close_others(true, forceit);
if (lastwin == firstwin) }
if (ONE_WINDOW) {
ex_win_close(forceit, curwin, NULL); ex_win_close(forceit, curwin, NULL);
}
} }
/* /*
@ -9593,6 +9599,16 @@ char_u *get_behave_arg(expand_T *xp, int idx)
return NULL; return NULL;
} }
// Function given to ExpandGeneric() to obtain the possible arguments of the
// ":messages {clear}" command.
char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
{
if (idx == 0) {
return (char_u *)"clear";
}
return NULL;
}
static TriState filetype_detect = kNone; static TriState filetype_detect = kNone;
static TriState filetype_plugin = kNone; static TriState filetype_plugin = kNone;
static TriState filetype_indent = kNone; static TriState filetype_indent = kNone;

View File

@ -374,10 +374,9 @@ int do_intthrow(struct condstack *cstack)
return TRUE; return TRUE;
} }
/* // Get an exception message that is to be stored in current_exception->value.
* Get an exception message that is to be stored in current_exception->value. char_u *get_exception_string(void *value, except_type_T type, char_u *cmdname,
*/ int *should_free)
char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should_free)
{ {
char_u *ret, *mesg; char_u *ret, *mesg;
char_u *p, *val; char_u *p, *val;
@ -435,13 +434,11 @@ char_u *get_exception_string(void *value, int type, char_u *cmdname, int *should
} }
/* // Throw a new exception. Return FAIL when out of memory or it was tried to
* Throw a new exception. Return FAIL when out of memory or it was tried to // throw an illegal user exception. "value" is the exception string for a
* throw an illegal user exception. "value" is the exception string for a // user or interrupt exception, or points to a message list in case of an
* user or interrupt exception, or points to a message list in case of an // error exception.
* error exception. static int throw_exception(void *value, except_type_T type, char_u *cmdname)
*/
static int throw_exception(void *value, int type, char_u *cmdname)
{ {
except_T *excp; except_T *excp;
int should_free; int should_free;

View File

@ -89,27 +89,28 @@ struct msglist {
struct msglist *next; /* next of several messages in a row */ struct msglist *next; /* next of several messages in a row */
}; };
// The exception types.
typedef enum
{
ET_USER, // exception caused by ":throw" command
ET_ERROR, // error exception
ET_INTERRUPT // interrupt exception triggered by Ctrl-C
} except_type_T;
/* /*
* Structure describing an exception. * Structure describing an exception.
* (don't use "struct exception", it's used by the math library). * (don't use "struct exception", it's used by the math library).
*/ */
typedef struct vim_exception except_T; typedef struct vim_exception except_T;
struct vim_exception { struct vim_exception {
int type; /* exception type */ except_type_T type; // exception type
char_u *value; /* exception value */ char_u *value; // exception value
struct msglist *messages; /* message(s) causing error exception */ struct msglist *messages; // message(s) causing error exception
char_u *throw_name; /* name of the throw point */ char_u *throw_name; // name of the throw point
linenr_T throw_lnum; /* line number of the throw point */ linenr_T throw_lnum; // line number of the throw point
except_T *caught; /* next exception on the caught stack */ except_T *caught; // next exception on the caught stack
}; };
/*
* The exception types.
*/
#define ET_USER 0 /* exception caused by ":throw" command */
#define ET_ERROR 1 /* error exception */
#define ET_INTERRUPT 2 /* interrupt exception triggered by Ctrl-C */
/* /*
* Structure to save the error/interrupt/exception state between calls to * Structure to save the error/interrupt/exception state between calls to
* enter_cleanup() and leave_cleanup(). Must be allocated as an automatic * enter_cleanup() and leave_cleanup(). Must be allocated as an automatic

View File

@ -4050,6 +4050,7 @@ ExpandFromContext (
} tab[] = { } tab[] = {
{ EXPAND_COMMANDS, get_command_name, false, true }, { EXPAND_COMMANDS, get_command_name, false, true },
{ EXPAND_BEHAVE, get_behave_arg, true, true }, { EXPAND_BEHAVE, get_behave_arg, true, true },
{ EXPAND_MESSAGES, get_messages_arg, true, true },
{ EXPAND_HISTORY, get_history_arg, true, true }, { EXPAND_HISTORY, get_history_arg, true, true },
{ EXPAND_USER_COMMANDS, get_user_commands, false, true }, { EXPAND_USER_COMMANDS, get_user_commands, false, true },
{ EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true }, { EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, false, true },

View File

@ -119,7 +119,7 @@ for i = 1, #events do
write_signature(bridge_output, ev, 'UI *ui') write_signature(bridge_output, ev, 'UI *ui')
bridge_output:write('\n{\n') bridge_output:write('\n{\n')
bridge_output:write(send) bridge_output:write(send)
bridge_output:write(' UI_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n')
end end
end end
@ -128,6 +128,7 @@ for i = 1, #events do
call_output:write('\n{\n') call_output:write('\n{\n')
if ev.remote_only then if ev.remote_only then
write_arglist(call_output, ev, false) write_arglist(call_output, ev, false)
call_output:write(' UI_LOG('..ev.name..', 0);\n')
call_output:write(' ui_event("'..ev.name..'", args);\n') call_output:write(' ui_event("'..ev.name..'", args);\n')
else else
call_output:write(' UI_CALL') call_output:write(' UI_CALL')

View File

@ -494,6 +494,7 @@ EXTERN int updating_screen INIT(= FALSE);
EXTERN win_T *firstwin; /* first window */ EXTERN win_T *firstwin; /* first window */
EXTERN win_T *lastwin; /* last window */ EXTERN win_T *lastwin; /* last window */
EXTERN win_T *prevwin INIT(= NULL); /* previous window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */
# define ONE_WINDOW (firstwin == lastwin)
/* /*
* When using this macro "break" only breaks out of the inner loop. Use "goto" * When using this macro "break" only breaks out of the inner loop. Use "goto"
* to break out of the tabpage loop. * to break out of the tabpage loop.

View File

@ -173,6 +173,53 @@ FILE *open_log_file(void)
return stderr; return stderr;
} }
#if defined(__linux__)
# include <execinfo.h>
void log_callstack(const char *const func_name, const int line_num)
{
void *trace[100];
int trace_size = backtrace(trace, ARRAY_SIZE(trace));
char exepath[MAXPATHL] = { 0 };
size_t exepathlen = MAXPATHL;
if (os_exepath(exepath, &exepathlen) != 0) {
abort();
}
assert(24 + exepathlen < IOSIZE); // Must fit in `cmdbuf` below.
do_log(DEBUG_LOG_LEVEL, func_name, line_num, true, "trace:");
char cmdbuf[IOSIZE + (20 * ARRAY_SIZE(trace))];
snprintf(cmdbuf, sizeof(cmdbuf), "addr2line -e %s -f -p", exepath);
for (int i = 1; i < trace_size; i++) {
char buf[20]; // 64-bit pointer 0xNNNNNNNNNNNNNNNN with leading space.
snprintf(buf, sizeof(buf), " %p", trace[i]);
xstrlcat(cmdbuf, buf, sizeof(cmdbuf));
}
// Now we have a command string like:
// addr2line -e /path/to/exe -f -p 0x123 0x456 ...
log_lock();
FILE *log_file = open_log_file();
if (log_file == NULL) {
goto end;
}
FILE *fp = popen(cmdbuf, "r");
char linebuf[IOSIZE];
while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) {
fprintf(log_file, " %s", linebuf);
}
pclose(fp);
if (log_file != stderr && log_file != stdout) {
fclose(log_file);
}
end:
log_unlock();
}
#endif
static bool do_log_to_file(FILE *log_file, int log_level, static bool do_log_to_file(FILE *log_file, int log_level,
const char *func_name, int line_num, bool eol, const char *func_name, int line_num, bool eol,
const char* fmt, ...) const char* fmt, ...)

View File

@ -61,6 +61,10 @@
__VA_ARGS__) __VA_ARGS__)
#endif #endif
#if defined(__linux__)
# define LOG_CALLSTACK() log_callstack(__func__, __LINE__)
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.h.generated.h" # include "log.h.generated.h"
#endif #endif

View File

@ -585,7 +585,7 @@ void free_all_mem(void)
p_ea = false; p_ea = false;
if (first_tabpage->tp_next != NULL) if (first_tabpage->tp_next != NULL)
do_cmdline_cmd("tabonly!"); do_cmdline_cmd("tabonly!");
if (firstwin != lastwin) if (!ONE_WINDOW)
do_cmdline_cmd("only!"); do_cmdline_cmd("only!");
/* Free all spell info. */ /* Free all spell info. */

View File

@ -1763,7 +1763,7 @@ int onepage(int dir, long count)
loff.fill = 0; loff.fill = 0;
if (dir == FORWARD) { if (dir == FORWARD) {
if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
/* Vi compatible scrolling */ /* Vi compatible scrolling */
if (p_window <= 2) if (p_window <= 2)
++curwin->w_topline; ++curwin->w_topline;
@ -1797,7 +1797,7 @@ int onepage(int dir, long count)
max_topfill(); max_topfill();
continue; continue;
} }
if (firstwin == lastwin && p_window > 0 && p_window < Rows - 1) { if (ONE_WINDOW && p_window > 0 && p_window < Rows - 1) {
/* Vi compatible scrolling (sort of) */ /* Vi compatible scrolling (sort of) */
if (p_window <= 2) if (p_window <= 2)
--curwin->w_topline; --curwin->w_topline;

View File

@ -117,12 +117,15 @@ void channel_teardown(void)
/// Creates an API channel by starting a process and connecting to its /// Creates an API channel by starting a process and connecting to its
/// stdin/stdout. stderr is handled by the job infrastructure. /// stdin/stdout. stderr is handled by the job infrastructure.
/// ///
/// @param argv The argument vector for the process. [consumed] /// @param proc process object
/// @return The channel id (> 0), on success. /// @param id (optional) channel id
/// 0, on error. /// @param source description of source function, rplugin name, TCP addr, etc
uint64_t channel_from_process(Process *proc, uint64_t id) ///
/// @return Channel id (> 0), on success. 0, on error.
uint64_t channel_from_process(Process *proc, uint64_t id, char *source)
{ {
Channel *channel = register_channel(kChannelTypeProc, id, proc->events); Channel *channel = register_channel(kChannelTypeProc, id, proc->events,
source);
incref(channel); // process channels are only closed by the exit_cb incref(channel); // process channels are only closed by the exit_cb
channel->data.proc = proc; channel->data.proc = proc;
@ -138,7 +141,8 @@ uint64_t channel_from_process(Process *proc, uint64_t id)
/// @param watcher The SocketWatcher ready to accept the connection /// @param watcher The SocketWatcher ready to accept the connection
void channel_from_connection(SocketWatcher *watcher) void channel_from_connection(SocketWatcher *watcher)
{ {
Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); Channel *channel = register_channel(kChannelTypeSocket, 0, NULL,
watcher->addr);
socket_watcher_accept(watcher, &channel->data.stream); socket_watcher_accept(watcher, &channel->data.stream);
incref(channel); // close channel only after the stream is closed incref(channel); // close channel only after the stream is closed
channel->data.stream.internal_close_cb = close_cb; channel->data.stream.internal_close_cb = close_cb;
@ -148,8 +152,9 @@ void channel_from_connection(SocketWatcher *watcher)
rstream_start(&channel->data.stream, receive_msgpack, channel); rstream_start(&channel->data.stream, receive_msgpack, channel);
} }
uint64_t channel_connect(bool tcp, const char *address, /// @param source description of source function, rplugin name, TCP addr, etc
int timeout, const char **error) uint64_t channel_connect(bool tcp, const char *address, int timeout,
char *source, const char **error)
{ {
if (!tcp) { if (!tcp) {
char *path = fix_fname(address); char *path = fix_fname(address);
@ -161,7 +166,7 @@ uint64_t channel_connect(bool tcp, const char *address,
xfree(path); xfree(path);
} }
Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); Channel *channel = register_channel(kChannelTypeSocket, 0, NULL, source);
if (!socket_connect(&main_loop, &channel->data.stream, if (!socket_connect(&main_loop, &channel->data.stream,
tcp, address, timeout, error)) { tcp, address, timeout, error)) {
decref(channel); decref(channel);
@ -329,11 +334,10 @@ bool channel_close(uint64_t id)
return true; return true;
} }
/// Creates an API channel from stdin/stdout. This is used when embedding /// Creates an API channel from stdin/stdout. Used to embed Nvim.
/// Neovim
void channel_from_stdio(void) void channel_from_stdio(void)
{ {
Channel *channel = register_channel(kChannelTypeStdio, 0, NULL); Channel *channel = register_channel(kChannelTypeStdio, 0, NULL, NULL);
incref(channel); // stdio channels are only closed on exit incref(channel); // stdio channels are only closed on exit
// read stream // read stream
rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE);
@ -346,7 +350,7 @@ void channel_from_stdio(void)
/// when an instance connects to its own named pipe. /// when an instance connects to its own named pipe.
uint64_t channel_create_internal(void) uint64_t channel_create_internal(void)
{ {
Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); Channel *channel = register_channel(kChannelTypeInternal, 0, NULL, NULL);
incref(channel); // internal channel lives until process exit incref(channel); // internal channel lives until process exit
return channel->id; return channel->id;
} }
@ -745,9 +749,12 @@ static void close_cb(Stream *stream, void *data)
decref(data); decref(data);
} }
/// @param source description of source function, rplugin name, TCP addr, etc
static Channel *register_channel(ChannelType type, uint64_t id, static Channel *register_channel(ChannelType type, uint64_t id,
MultiQueue *events) MultiQueue *events, char *source)
{ {
// Jobs and channels share the same id namespace.
assert(id == 0 || !pmap_get(uint64_t)(channels, id));
Channel *rv = xmalloc(sizeof(Channel)); Channel *rv = xmalloc(sizeof(Channel));
rv->events = events ? events : multiqueue_new_child(main_loop.events); rv->events = events ? events : multiqueue_new_child(main_loop.events);
rv->type = type; rv->type = type;
@ -761,6 +768,14 @@ static Channel *register_channel(ChannelType type, uint64_t id,
kv_init(rv->call_stack); kv_init(rv->call_stack);
kv_init(rv->delayed_notifications); kv_init(rv->delayed_notifications);
pmap_put(uint64_t)(channels, rv->id, rv); pmap_put(uint64_t)(channels, rv->id, rv);
ILOG("new channel %" PRIu64 " (%s): %s", rv->id,
(type == kChannelTypeProc ? "proc"
: (type == kChannelTypeSocket ? "socket"
: (type == kChannelTypeStdio ? "stdio"
: (type == kChannelTypeInternal ? "internal" : "?")))),
(source ? source : "?"));
return rv; return rv;
} }

View File

@ -3657,6 +3657,39 @@ nv_gd (
} }
} }
// Return true if line[offset] is not inside a C-style comment or string, false
// otherwise.
static bool is_ident(char_u *line, int offset)
{
bool incomment = false;
int instring = 0;
int prev = 0;
for (int i = 0; i < offset && line[i] != NUL; i++) {
if (instring != 0) {
if (prev != '\\' && line[i] == instring) {
instring = 0;
}
} else if ((line[i] == '"' || line[i] == '\'') && !incomment) {
instring = line[i];
} else {
if (incomment) {
if (prev == '*' && line[i] == '/') {
incomment = false;
}
} else if (prev == '/' && line[i] == '*') {
incomment = true;
} else if (prev == '/' && line[i] == '/') {
return false;
}
}
prev = line[i];
}
return incomment == false && instring == 0;
}
/* /*
* Search for variable declaration of "ptr[len]". * Search for variable declaration of "ptr[len]".
* When "locally" is true in the current function ("gd"), otherwise in the * When "locally" is true in the current function ("gd"), otherwise in the
@ -3683,6 +3716,7 @@ find_decl (
bool retval = true; bool retval = true;
bool incll; bool incll;
int searchflags = flags_arg; int searchflags = flags_arg;
bool valid;
pat = xmalloc(len + 7); pat = xmalloc(len + 7);
@ -3717,6 +3751,7 @@ find_decl (
/* Search forward for the identifier, ignore comment lines. */ /* Search forward for the identifier, ignore comment lines. */
clearpos(&found_pos); clearpos(&found_pos);
for (;; ) { for (;; ) {
valid = false;
t = searchit(curwin, curbuf, &curwin->w_cursor, FORWARD, t = searchit(curwin, curbuf, &curwin->w_cursor, FORWARD,
pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL); pat, 1L, searchflags, RE_LAST, (linenr_T)0, NULL);
if (curwin->w_cursor.lnum >= old_pos.lnum) if (curwin->w_cursor.lnum >= old_pos.lnum)
@ -3747,20 +3782,35 @@ find_decl (
curwin->w_cursor.col = 0; curwin->w_cursor.col = 0;
continue; continue;
} }
if (!locally) /* global search: use first match found */ valid = is_ident(get_cursor_line_ptr(), curwin->w_cursor.col);
// If the current position is not a valid identifier and a previous match is
// present, favor that one instead.
if (!valid && found_pos.lnum != 0) {
curwin->w_cursor = found_pos;
break; break;
if (curwin->w_cursor.lnum >= par_pos.lnum) { }
/* If we previously found a valid position, use it. */ // global search: use first match found
if (found_pos.lnum != 0) if (valid && !locally) {
break;
}
if (valid && curwin->w_cursor.lnum >= par_pos.lnum) {
// If we previously found a valid position, use it.
if (found_pos.lnum != 0) {
curwin->w_cursor = found_pos; curwin->w_cursor = found_pos;
}
break; break;
} }
// For finding a local variable and the match is before the "{" search // For finding a local variable and the match is before the "{" or
// to find a later match. For K&R style function declarations this // inside a comment, continue searching. For K&R style function
// skips the function header without types. Remove SEARCH_START from // declarations this skips the function header without types.
// flags to avoid getting stuck at one position. if (!valid) {
found_pos = curwin->w_cursor; clearpos(&found_pos);
} else {
found_pos = curwin->w_cursor;
}
// Remove SEARCH_START from flags to avoid getting stuck at one position.
searchflags &= ~SEARCH_START; searchflags &= ~SEARCH_START;
} }

View File

@ -4078,7 +4078,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
} }
/* Change window height NOW */ /* Change window height NOW */
if (lastwin != firstwin) { if (!ONE_WINDOW) {
if (pp == &p_wh && curwin->w_height < p_wh) if (pp == &p_wh && curwin->w_height < p_wh)
win_setheight((int)p_wh); win_setheight((int)p_wh);
if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh)
@ -4107,7 +4107,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value,
} }
/* Change window width NOW */ /* Change window width NOW */
if (lastwin != firstwin && curwin->w_width < p_wiw) if (!ONE_WINDOW && curwin->w_width < p_wiw)
win_setwidth((int)p_wiw); win_setwidth((int)p_wiw);
} }
/* 'winminwidth' */ /* 'winminwidth' */
@ -5239,7 +5239,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
void comp_col(void) void comp_col(void)
{ {
int last_has_status = (p_ls == 2 || (p_ls == 1 && firstwin != lastwin)); int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW));
sc_col = 0; sc_col = 0;
ru_col = 0; ru_col = 0;

View File

@ -1878,7 +1878,7 @@ win_found:
* If there is only one window and it is the quickfix window, create a * If there is only one window and it is the quickfix window, create a
* new one above the quickfix window. * new one above the quickfix window.
*/ */
if (((firstwin == lastwin) && bt_quickfix(curbuf)) || !usable_win) { if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) {
flags = WSP_ABOVE; flags = WSP_ABOVE;
if (ll_ref != NULL) if (ll_ref != NULL)
flags |= WSP_NEWLOC; flags |= WSP_NEWLOC;

View File

@ -991,7 +991,7 @@ static void win_update(win_T *wp)
* first. */ * first. */
if (mid_start == 0) { if (mid_start == 0) {
mid_end = wp->w_height; mid_end = wp->w_height;
if (lastwin == firstwin) { if (ONE_WINDOW) {
/* Clear the screen when it was not done by win_del_lines() or /* Clear the screen when it was not done by win_del_lines() or
* win_ins_lines() above, "screen_cleared" is FALSE or MAYBE * win_ins_lines() above, "screen_cleared" is FALSE or MAYBE
* then. */ * then. */
@ -7160,7 +7160,7 @@ static int fillchar_status(int *attr, win_T *wp)
* window differs, or the fillchars differ, or this is not the * window differs, or the fillchars differ, or this is not the
* current window */ * current window */
if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC) if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
|| !is_curwin || firstwin == lastwin) || !is_curwin || ONE_WINDOW)
|| (fill_stl != fill_stlnc))) { || (fill_stl != fill_stlnc))) {
return fill; return fill;
} }

View File

@ -79,7 +79,11 @@ let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
func RunTheTest(test) func RunTheTest(test)
echo 'Executing ' . a:test echo 'Executing ' . a:test
if exists("*SetUp") if exists("*SetUp")
call SetUp() try
call SetUp()
catch
call add(v:errors, 'Caught exception in SetUp() before ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
endtry
endif endif
call add(s:messages, 'Executing ' . a:test) call add(s:messages, 'Executing ' . a:test)
@ -94,7 +98,11 @@ func RunTheTest(test)
endtry endtry
if exists("*TearDown") if exists("*TearDown")
call TearDown() try
call TearDown()
catch
call add(v:errors, 'Caught exception in TearDown() after ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
endtry
endif endif
" Close any extra windows and make the current one not modified. " Close any extra windows and make the current one not modified.

View File

@ -130,6 +130,11 @@ func Test_getcompletion()
let l = getcompletion('dark', 'highlight') let l = getcompletion('dark', 'highlight')
call assert_equal([], l) call assert_equal([], l)
let l = getcompletion('', 'messages')
call assert_true(index(l, 'clear') >= 0)
let l = getcompletion('not', 'messages')
call assert_equal([], l)
if has('cscope') if has('cscope')
let l = getcompletion('', 'cscope') let l = getcompletion('', 'cscope')
let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show'] let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show']

View File

@ -1,22 +1,277 @@
" Test commands that jump somewhere. " Test commands that jump somewhere.
func Test_geeDEE() " Create a new buffer using "lines" and place the cursor on the word after the
" first occurrence of return and invoke "cmd". The cursor should now be
" positioned at the given line and col.
func XTest_goto_decl(cmd, lines, line, col)
new new
call setline(1, ["Filename x;", "", "int Filename", "int func() {", "Filename y;"]) call setline(1, a:lines)
/y;/ /return/
normal gD normal! W
call assert_equal(1, line('.')) execute 'norm! ' . a:cmd
call assert_equal(a:line, line('.'))
call assert_equal(a:col, col('.'))
quit! quit!
endfunc endfunc
func Test_gee_dee() func Test_gD()
new let lines = [
call setline(1, ["int x;", "", "int func(int x)", "{", " return x;", "}"]) \ 'int x;',
/return/ \ '',
normal $hgd \ 'int func(void)',
call assert_equal(3, line('.')) \ '{',
call assert_equal(14, col('.')) \ ' return x;',
quit! \ '}',
\ ]
call XTest_goto_decl('gD', lines, 1, 5)
endfunc
func Test_gD_too()
let lines = [
\ 'Filename x;',
\ '',
\ 'int Filename',
\ 'int func() {',
\ ' Filename x;',
\ ' return x;',
\ ]
call XTest_goto_decl('gD', lines, 1, 10)
endfunc
func Test_gD_comment()
let lines = [
\ '/* int x; */',
\ 'int x;',
\ '',
\ 'int func(void)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_inline_comment()
let lines = [
\ 'int y /* , x */;',
\ 'int x;',
\ '',
\ 'int func(void)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string()
let lines = [
\ 'char *s[] = "x";',
\ 'int x = 1;',
\ '',
\ 'int func(void)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gD_string_same_line()
let lines = [
\ 'char *s[] = "x", int x = 1;',
\ '',
\ 'int func(void)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gD', lines, 1, 22)
endfunc
func Test_gD_char()
let lines = [
\ "char c = 'x';",
\ 'int x = 1;',
\ '',
\ 'int func(void)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gD', lines, 2, 5)
endfunc
func Test_gd()
let lines = [
\ 'int x;',
\ '',
\ 'int func(int x)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 3, 14)
endfunc
func Test_gd_not_local()
let lines = [
\ 'int func1(void)',
\ '{',
\ ' return x;',
\ '}',
\ '',
\ 'int func2(int x)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_kr_style()
let lines = [
\ 'int func(x)',
\ ' int x;',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 2, 7)
endfunc
func Test_gd_missing_braces()
let lines = [
\ 'def func1(a)',
\ ' a + 1',
\ 'end',
\ '',
\ 'a = 1',
\ '',
\ 'def func2()',
\ ' return a',
\ 'end',
\ ]
call XTest_goto_decl('gd', lines, 1, 11)
endfunc
func Test_gd_comment()
let lines = [
\ 'int func(void)',
\ '{',
\ ' /* int x; */',
\ ' int x;',
\ ' return x;',
\ '}',
\]
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_comment_in_string()
let lines = [
\ 'int func(void)',
\ '{',
\ ' char *s ="//"; int x;',
\ ' int x;',
\ ' return x;',
\ '}',
\]
call XTest_goto_decl('gd', lines, 3, 22)
endfunc
func Test_gd_string_in_comment()
set comments=
let lines = [
\ 'int func(void)',
\ '{',
\ ' /* " */ int x;',
\ ' int x;',
\ ' return x;',
\ '}',
\]
call XTest_goto_decl('gd', lines, 3, 15)
set comments&
endfunc
func Test_gd_inline_comment()
let lines = [
\ 'int func(/* x is an int */ int x)',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 1, 32)
endfunc
func Test_gd_inline_comment_only()
let lines = [
\ 'int func(void) /* one lonely x */',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 3, 10)
endfunc
func Test_gd_inline_comment_body()
let lines = [
\ 'int func(void)',
\ '{',
\ ' int y /* , x */;',
\ '',
\ ' for (/* int x = 0 */; y < 2; y++);',
\ '',
\ ' int x = 0;',
\ '',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 7, 7)
endfunc
func Test_gd_trailing_multiline_comment()
let lines = [
\ 'int func(int x) /* x is an int */',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_trailing_comment()
let lines = [
\ 'int func(int x) // x is an int',
\ '{',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 1, 14)
endfunc
func Test_gd_string()
let lines = [
\ 'int func(void)',
\ '{',
\ ' char *s = "x";',
\ ' int x = 1;',
\ '',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 4, 7)
endfunc
func Test_gd_string_only()
let lines = [
\ 'int func(void)',
\ '{',
\ ' char *s = "x";',
\ '',
\ ' return x;',
\ '}',
\ ]
call XTest_goto_decl('gd', lines, 5, 10)
endfunc endfunc
" Check that setting 'cursorline' does not change curswant " Check that setting 'cursorline' does not change curswant

View File

@ -1007,7 +1007,7 @@ static void tui_flush(UI *ui)
size_t nrevents = loop_size(data->loop); size_t nrevents = loop_size(data->loop);
if (nrevents > TOO_MANY_EVENTS) { if (nrevents > TOO_MANY_EVENTS) {
ILOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents); WLOG("TUI event-queue flooded (thread_events=%zu); purging", nrevents);
// Back-pressure: UI events may accumulate much faster than the terminal // Back-pressure: UI events may accumulate much faster than the terminal
// device can serve them. Even if SIGINT/CTRL-C is received, user must still // device can serve them. Even if SIGINT/CTRL-C is received, user must still
// wait for the TUI event-queue to drain, and if there are ~millions of // wait for the TUI event-queue to drain, and if there are ~millions of
@ -1690,7 +1690,7 @@ static const char *tui_get_stty_erase(void)
if (tcgetattr(input_global_fd(), &t) != -1) { if (tcgetattr(input_global_fd(), &t) != -1) {
stty_erase[0] = (char)t.c_cc[VERASE]; stty_erase[0] = (char)t.c_cc[VERASE];
stty_erase[1] = '\0'; stty_erase[1] = '\0';
ILOG("stty/termios:erase=%s", stty_erase); DLOG("stty/termios:erase=%s", stty_erase);
} }
#endif #endif
return stty_erase; return stty_erase;
@ -1707,12 +1707,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value,
} }
if (strequal(name, "key_backspace")) { if (strequal(name, "key_backspace")) {
ILOG("libtermkey:kbs=%s", value); DLOG("libtermkey:kbs=%s", value);
if (stty_erase[0] != 0) { if (stty_erase[0] != 0) {
return stty_erase; return stty_erase;
} }
} else if (strequal(name, "key_dc")) { } else if (strequal(name, "key_dc")) {
ILOG("libtermkey:kdch1=%s", value); DLOG("libtermkey:kdch1=%s", value);
// Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>."
if (value != NULL && strequal(stty_erase, value)) { if (value != NULL && strequal(stty_erase, value)) {
return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR;

View File

@ -8,6 +8,7 @@
#include <limits.h> #include <limits.h>
#include "nvim/vim.h" #include "nvim/vim.h"
#include "nvim/log.h"
#include "nvim/ui.h" #include "nvim/ui.h"
#include "nvim/charset.h" #include "nvim/charset.h"
#include "nvim/cursor.h" #include "nvim/cursor.h"
@ -59,6 +60,27 @@ static int busy = 0;
static int height, width; static int height, width;
static int old_mode_idx = -1; static int old_mode_idx = -1;
#if MIN_LOG_LEVEL > DEBUG_LOG_LEVEL
# define UI_LOG(funname, ...)
#else
static size_t uilog_seen = 0;
static char uilog_last_event[1024] = { 0 };
# define UI_LOG(funname, ...) \
do { \
if (strequal(uilog_last_event, STR(funname))) { \
uilog_seen++; \
} else { \
if (uilog_seen > 0) { \
do_log(DEBUG_LOG_LEVEL, "ui", 0, true, \
"%s (+%zu times...)", uilog_last_event, uilog_seen); \
} \
DLOG("ui: " STR(funname)); \
uilog_seen = 0; \
xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
} \
} while (0)
#endif
// UI_CALL invokes a function on all registered UI instances. The functions can // UI_CALL invokes a function on all registered UI instances. The functions can
// have 0-5 arguments (configurable by SELECT_NTH). // have 0-5 arguments (configurable by SELECT_NTH).
// //
@ -67,6 +89,7 @@ static int old_mode_idx = -1;
# define UI_CALL(funname, ...) \ # define UI_CALL(funname, ...) \
do { \ do { \
flush_cursor_update(); \ flush_cursor_update(); \
UI_LOG(funname, 0); \
for (size_t i = 0; i < ui_count; i++) { \ for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \ UI *ui = uis[i]; \
UI_CALL_MORE(funname, __VA_ARGS__); \ UI_CALL_MORE(funname, __VA_ARGS__); \
@ -76,6 +99,7 @@ static int old_mode_idx = -1;
# define UI_CALL(...) \ # define UI_CALL(...) \
do { \ do { \
flush_cursor_update(); \ flush_cursor_update(); \
UI_LOG(__VA_ARGS__, 0); \
for (size_t i = 0; i < ui_count; i++) { \ for (size_t i = 0; i < ui_count; i++) { \
UI *ui = uis[i]; \ UI *ui = uis[i]; \
UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \ UI_CALL_HELPER(CNT(__VA_ARGS__), __VA_ARGS__); \
@ -85,6 +109,7 @@ static int old_mode_idx = -1;
#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) #define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore)
#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 #define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6
#define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__)
// Resolves to UI_CALL_MORE or UI_CALL_ZERO.
#define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__)
#define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) #define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__)
#define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) #define UI_CALL_ZERO(method) if (ui->method) ui->method(ui)

View File

@ -24,30 +24,10 @@
#define UI(b) (((UIBridgeData *)b)->ui) #define UI(b) (((UIBridgeData *)b)->ui)
#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL
static size_t uilog_seen = 0;
static argv_callback uilog_event = NULL;
#define UI_CALL(ui, name, argc, ...) \
do { \
if (uilog_event == ui_bridge_##name##_event) { \
uilog_seen++; \
} else { \
if (uilog_seen > 0) { \
DLOG("UI bridge: ...%zu times", uilog_seen); \
} \
DLOG("UI bridge: " STR(name)); \
uilog_seen = 0; \
uilog_event = ui_bridge_##name##_event; \
} \
((UIBridgeData *)ui)->scheduler( \
event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)); \
} while (0)
#else
// Schedule a function call on the UI bridge thread. // Schedule a function call on the UI bridge thread.
#define UI_CALL(ui, name, argc, ...) \ #define UI_BRIDGE_CALL(ui, name, argc, ...) \
((UIBridgeData *)ui)->scheduler( \ ((UIBridgeData *)ui)->scheduler( \
event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui))
#endif
#define INT2PTR(i) ((void *)(intptr_t)i) #define INT2PTR(i) ((void *)(intptr_t)i)
#define PTR2INT(p) ((Integer)(intptr_t)p) #define PTR2INT(p) ((Integer)(intptr_t)p)
@ -128,7 +108,7 @@ static void ui_bridge_stop(UI *b)
{ {
UIBridgeData *bridge = (UIBridgeData *)b; UIBridgeData *bridge = (UIBridgeData *)b;
bool stopped = bridge->stopped = false; bool stopped = bridge->stopped = false;
UI_CALL(b, stop, 1, b); UI_BRIDGE_CALL(b, stop, 1, b);
for (;;) { for (;;) {
uv_mutex_lock(&bridge->mutex); uv_mutex_lock(&bridge->mutex);
stopped = bridge->stopped; stopped = bridge->stopped;
@ -154,7 +134,7 @@ static void ui_bridge_highlight_set(UI *b, HlAttrs attrs)
{ {
HlAttrs *a = xmalloc(sizeof(HlAttrs)); HlAttrs *a = xmalloc(sizeof(HlAttrs));
*a = attrs; *a = attrs;
UI_CALL(b, highlight_set, 2, b, a); UI_BRIDGE_CALL(b, highlight_set, 2, b, a);
} }
static void ui_bridge_highlight_set_event(void **argv) static void ui_bridge_highlight_set_event(void **argv)
{ {
@ -167,7 +147,7 @@ static void ui_bridge_suspend(UI *b)
{ {
UIBridgeData *data = (UIBridgeData *)b; UIBridgeData *data = (UIBridgeData *)b;
uv_mutex_lock(&data->mutex); uv_mutex_lock(&data->mutex);
UI_CALL(b, suspend, 1, b); UI_BRIDGE_CALL(b, suspend, 1, b);
data->ready = false; data->ready = false;
// suspend the main thread until CONTINUE is called by the UI thread // suspend the main thread until CONTINUE is called by the UI thread
while (!data->ready) { while (!data->ready) {

View File

@ -641,7 +641,7 @@ static const int included_patches[] = {
// 91, // 91,
// 90, // 90,
// 89 NA // 89 NA
// 88, 88,
// 87 NA // 87 NA
// 86, // 86,
85, 85,
@ -654,20 +654,20 @@ static const int included_patches[] = {
78, 78,
// 77 NA // 77 NA
// 76 NA // 76 NA
// 75, 75,
// 74, // 74,
// 73, 73,
// 72 NA // 72 NA
// 71 NA // 71 NA
// 70 NA // 70 NA
// 69, 69,
68, 68,
// 67 NA // 67 NA
66, 66,
// 65 NA // 65 NA
64, 64,
// 63, // 63,
// 62, 62,
// 61 NA // 61 NA
60, 60,
// 59 NA // 59 NA
@ -695,7 +695,7 @@ static const int included_patches[] = {
37, 37,
// 36 NA // 36 NA
35, 35,
// 34, 34,
33, 33,
32, 32,
31, 31,
@ -704,9 +704,9 @@ static const int included_patches[] = {
// 28 NA // 28 NA
// 27 NA // 27 NA
26, 26,
// 25, 25,
// 24 NA // 24 NA
// 23, 23,
// 22 NA // 22 NA
// 21, // 21,
// 20, // 20,

View File

@ -163,6 +163,7 @@ enum {
EXPAND_SYNTIME, EXPAND_SYNTIME,
EXPAND_USER_ADDR_TYPE, EXPAND_USER_ADDR_TYPE,
EXPAND_PACKADD, EXPAND_PACKADD,
EXPAND_MESSAGES,
}; };

View File

@ -193,7 +193,7 @@ newwindow:
/* cursor to previous window with wrap around */ /* cursor to previous window with wrap around */
case 'W': case 'W':
CHECK_CMDWIN CHECK_CMDWIN
if (firstwin == lastwin && Prenum != 1) /* just one window */ if (ONE_WINDOW && Prenum != 1) /* just one window */
beep_flush(); beep_flush();
else { else {
if (Prenum) { /* go to specified window */ if (Prenum) { /* go to specified window */
@ -574,7 +574,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
oldwin = curwin; oldwin = curwin;
/* add a status line when p_ls == 1 and splitting the first window */ /* add a status line when p_ls == 1 and splitting the first window */
if (lastwin == firstwin && p_ls == 1 && oldwin->w_status_height == 0) { if (ONE_WINDOW && p_ls == 1 && oldwin->w_status_height == 0) {
if (oldwin->w_height <= p_wmh && new_wp == NULL) { if (oldwin->w_height <= p_wmh && new_wp == NULL) {
EMSG(_(e_noroom)); EMSG(_(e_noroom));
return FAIL; return FAIL;
@ -1182,7 +1182,7 @@ static void win_exchange(long Prenum)
win_T *wp2; win_T *wp2;
int temp; int temp;
if (lastwin == firstwin) { /* just one window */ if (ONE_WINDOW) { /* just one window */
beep_flush(); beep_flush();
return; return;
} }
@ -1271,7 +1271,7 @@ static void win_rotate(int upwards, int count)
frame_T *frp; frame_T *frp;
int n; int n;
if (firstwin == lastwin) { /* nothing to do */ if (ONE_WINDOW) { /* nothing to do */
beep_flush(); beep_flush();
return; return;
} }
@ -1343,7 +1343,7 @@ static void win_totop(int size, int flags)
int dir; int dir;
int height = curwin->w_height; int height = curwin->w_height;
if (lastwin == firstwin) { if (ONE_WINDOW) {
beep_flush(); beep_flush();
return; return;
} }
@ -1728,7 +1728,7 @@ void close_windows(buf_T *buf, int keep_curwin)
++RedrawingDisabled; ++RedrawingDisabled;
for (win_T *wp = firstwin; wp != NULL && lastwin != firstwin; ) { for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW; ) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin) if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) { && !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
if (win_close(wp, false) == FAIL) { if (win_close(wp, false) == FAIL) {
@ -1810,7 +1810,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf,
tabpage_T *prev_curtab) tabpage_T *prev_curtab)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
if (firstwin != lastwin) { if (!ONE_WINDOW) {
return false; return false;
} }
buf_T *old_curbuf = curbuf; buf_T *old_curbuf = curbuf;
@ -2194,7 +2194,7 @@ winframe_remove (
/* /*
* If there is only one window there is nothing to remove. * If there is only one window there is nothing to remove.
*/ */
if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin)
return NULL; return NULL;
/* /*
@ -2331,7 +2331,7 @@ win_altframe (
frame_T *frp; frame_T *frp;
int b; int b;
if (tp == NULL ? firstwin == lastwin : tp->tp_firstwin == tp->tp_lastwin) if (tp == NULL ? ONE_WINDOW : tp->tp_firstwin == tp->tp_lastwin)
/* Last window in this tab page, will go to next tab page. */ /* Last window in this tab page, will go to next tab page. */
return alt_tabpage()->tp_curwin->w_frame; return alt_tabpage()->tp_curwin->w_frame;
@ -2851,7 +2851,7 @@ close_others (
win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer));
} }
if (message && lastwin != firstwin) if (message && !ONE_WINDOW)
EMSG(_("E445: Other window contains changes")); EMSG(_("E445: Other window contains changes"));
} }
@ -5173,7 +5173,7 @@ last_status (
{ {
/* Don't make a difference between horizontal or vertical split. */ /* Don't make a difference between horizontal or vertical split. */
last_status_rec(topframe, (p_ls == 2 last_status_rec(topframe, (p_ls == 2
|| (p_ls == 1 && (morewin || lastwin != firstwin)))); || (p_ls == 1 && (morewin || !ONE_WINDOW))));
} }
static void last_status_rec(frame_T *fr, int statusline) static void last_status_rec(frame_T *fr, int statusline)