aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig2
-rw-r--r--CMakeLists.txt26
-rw-r--r--Makefile11
-rw-r--r--README.md2
-rw-r--r--cmake/GenerateHelptags.cmake.in2
-rw-r--r--cmake/LuaHelpers.cmake2
-rw-r--r--cmake/RunLuacheck.cmake2
-rw-r--r--cmake/RunTests.cmake2
-rw-r--r--runtime/autoload/man.vim11
-rw-r--r--runtime/doc/eval.txt8
-rw-r--r--runtime/doc/starting.txt2
-rw-r--r--runtime/doc/syntax.txt7
-rw-r--r--runtime/doc/ui.txt113
-rwxr-xr-xscripts/vim-patch.sh6
-rwxr-xr-xsrc/clint.py24
-rw-r--r--src/nvim/api/ui.c29
-rw-r--r--src/nvim/api/ui_events.in.h2
-rw-r--r--src/nvim/buffer.c7
-rw-r--r--src/nvim/eval.c18
-rw-r--r--src/nvim/eval.lua2
-rw-r--r--src/nvim/ex_cmds.c18
-rw-r--r--src/nvim/ex_cmds2.c1
-rw-r--r--src/nvim/ex_docmd.c26
-rw-r--r--src/nvim/ex_getln.c14
-rw-r--r--src/nvim/globals.h6
-rw-r--r--src/nvim/log.c6
-rw-r--r--src/nvim/main.c19
-rw-r--r--src/nvim/memline.c1
-rw-r--r--src/nvim/move.c5
-rw-r--r--src/nvim/normal.c3
-rw-r--r--src/nvim/option.c33
-rw-r--r--src/nvim/os_unix.c3
-rw-r--r--src/nvim/screen.c14
-rw-r--r--src/nvim/syntax.c2
-rw-r--r--src/nvim/terminal.c1
-rw-r--r--src/nvim/testdir/test_alot.vim1
-rw-r--r--src/nvim/testdir/test_expr.vim3
-rw-r--r--src/nvim/testdir/test_getcwd.vim12
-rw-r--r--src/nvim/testdir/test_listdict.vim48
-rw-r--r--src/nvim/testdir/test_sort.vim85
-rw-r--r--src/nvim/testdir/test_suspend.vim51
-rw-r--r--src/nvim/testdir/test_unlet.vim34
-rw-r--r--src/nvim/tui/tui.c150
-rw-r--r--src/nvim/ugrid.c59
-rw-r--r--src/nvim/ugrid.h4
-rw-r--r--src/nvim/ui.h4
-rw-r--r--src/nvim/undo.c2
-rw-r--r--test/functional/api/version_spec.lua2
-rw-r--r--test/functional/api/vim_spec.lua2
-rw-r--r--test/functional/core/startup_spec.lua6
-rw-r--r--test/functional/ex_cmds/recover_spec.lua76
-rw-r--r--test/functional/ex_cmds/swapfile_preserve_recover_spec.lua154
-rw-r--r--test/functional/options/shortmess_spec.lua99
-rw-r--r--test/functional/terminal/tui_spec.lua8
-rw-r--r--test/functional/ui/cmdline_spec.lua6
-rw-r--r--test/functional/ui/embed_spec.lua8
-rw-r--r--test/functional/ui/highlight_spec.lua22
-rw-r--r--test/functional/ui/options_spec.lua10
-rw-r--r--test/functional/ui/screen.lua33
-rw-r--r--test/functional/ui/screen_basic_spec.lua4
-rw-r--r--test/helpers.lua53
-rw-r--r--third-party/CMakeLists.txt6
62 files changed, 982 insertions, 390 deletions
diff --git a/.editorconfig b/.editorconfig
index c0b80fdf47..b117784d13 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -5,7 +5,7 @@ indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
-charset = utf_8
+charset = utf-8
[{Makefile,**/Makefile,runtime/doc/*.txt}]
indent_style = tab
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 02b555d540..fa020bf55c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -96,8 +96,8 @@ endif()
# Set default build type.
if(NOT CMAKE_BUILD_TYPE)
- message(STATUS "CMAKE_BUILD_TYPE not given, defaulting to 'Debug'.")
- set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build." FORCE)
+ message(STATUS "CMAKE_BUILD_TYPE not given, defaulting to 'Debug'")
+ set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build" FORCE)
endif()
# Set available build types for CMake GUIs.
@@ -137,7 +137,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Default to -O2 on release builds.
if(CMAKE_C_FLAGS_RELEASE MATCHES "-O3")
- message(STATUS "Replacing -O3 in CMAKE_C_FLAGS_RELEASE with -O2.")
+ message(STATUS "Replacing -O3 in CMAKE_C_FLAGS_RELEASE with -O2")
string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
@@ -205,12 +205,12 @@ main(void)
" HAS_ACCEPTABLE_FORTIFY)
if(NOT HAS_ACCEPTABLE_FORTIFY)
- message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1.")
+ message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1")
# Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE).
STRING(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}")
STRING(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" )
if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "")
- message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}.")
+ message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}")
endif()
# -U in add_definitions doesn't end up in the correct spot, so we add it to
# the flags variable instead.
@@ -222,7 +222,7 @@ endif()
if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR
CMAKE_SHARED_LINKER_FLAGS MATCHES "--sort-common" OR
CMAKE_MODULE_LINKER_FLAGS MATCHES "--sort-common")
- message(STATUS "Removing --sort-common from linker flags.")
+ message(STATUS "Removing --sort-common from linker flags")
string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS}")
string(REGEX REPLACE ",--sort-common(=[^,]+)?" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
@@ -309,10 +309,10 @@ if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
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)
- message(STATUS "Travis/QuickBuild CI build enabled.")
+ message(STATUS "Travis/QuickBuild CI build enabled")
add_definitions(-Werror)
if(DEFINED ENV{BUILD_32BIT})
# Get some test coverage for unsigned char
@@ -433,11 +433,11 @@ option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
if((CLANG_ASAN_UBSAN AND CLANG_MSAN)
OR (CLANG_ASAN_UBSAN AND CLANG_TSAN)
OR (CLANG_MSAN AND CLANG_TSAN))
- message(FATAL_ERROR "Sanitizers cannot be enabled simultaneously.")
+ message(FATAL_ERROR "Sanitizers cannot be enabled simultaneously")
endif()
if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
- message(FATAL_ERROR "Sanitizers are only supported for Clang.")
+ message(FATAL_ERROR "Sanitizers are only supported for Clang")
endif()
if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD|FreeBSD|Windows") # see #5318
@@ -449,7 +449,7 @@ endif()
if(ENABLE_JEMALLOC)
if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN)
- message(STATUS "Sanitizers have been enabled; don't use jemalloc.")
+ message(STATUS "Sanitizers enabled; disabling jemalloc")
else()
find_package(JeMalloc REQUIRED)
include_directories(SYSTEM ${JEMALLOC_INCLUDE_DIRS})
@@ -505,10 +505,10 @@ else()
endif()
if(NOT LUA_PRG_WORKS)
- message(FATAL_ERROR "A suitable Lua interpreter was not found.")
+ message(FATAL_ERROR "Failed to find a Lua 5.1-compatible interpreter")
endif()
-message(STATUS "Using the Lua interpreter ${LUA_PRG}.")
+message(STATUS "Using Lua interpreter: ${LUA_PRG}")
# Setup busted.
find_program(BUSTED_PRG NAMES busted busted.bat)
diff --git a/Makefile b/Makefile
index 6cbce899f0..a741c564e2 100644
--- a/Makefile
+++ b/Makefile
@@ -44,10 +44,11 @@ BUILD_CMD = $(BUILD_TOOL) $(VERBOSE_FLAG)
# Extra CMake flags which extend the default set
CMAKE_EXTRA_FLAGS ?=
DEPS_CMAKE_FLAGS ?=
-USE_BUNDLED_DEPS ?=
+# Back-compat: USE_BUNDLED_DEPS was the old name.
+USE_BUNDLED ?= $(USE_BUNDLED_DEPS)
-ifneq (,$(USE_BUNDLED_DEPS))
- BUNDLED_CMAKE_FLAG := -DUSE_BUNDLED=$(USE_BUNDLED_DEPS)
+ifneq (,$(USE_BUNDLED))
+ BUNDLED_CMAKE_FLAG := -DUSE_BUNDLED=$(USE_BUNDLED)
endif
ifneq (,$(findstring functionaltest-lua,$(MAKECMDGOALS)))
@@ -76,12 +77,12 @@ build/.ran-cmake: | deps
touch $@
deps: | build/.ran-third-party-cmake
-ifeq ($(call filter-true,$(USE_BUNDLED_DEPS)),)
+ifeq ($(call filter-true,$(USE_BUNDLED)),)
+$(BUILD_CMD) -C $(DEPS_BUILD_DIR)
endif
build/.ran-third-party-cmake:
-ifeq ($(call filter-true,$(USE_BUNDLED_DEPS)),)
+ifeq ($(call filter-true,$(USE_BUNDLED)),)
mkdir -p $(DEPS_BUILD_DIR)
cd $(DEPS_BUILD_DIR) && \
$(CMAKE_PRG) -G '$(BUILD_TYPE)' $(BUNDLED_CMAKE_FLAG) $(BUNDLED_LUA_CMAKE_FLAG) \
diff --git a/README.md b/README.md
index d3b3752625..8be9d2d2e7 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@ To list all targets:
cmake --build build --target help
-To skip "bundled" dependencies define `USE_BUNDLED_DEPS=NO` (CMake option: `USE_BUNDLED=NO`).
+To skip "bundled" (`third-party/*`) dependencies define `USE_BUNDLED=NO`.
See [the wiki](https://github.com/neovim/neovim/wiki/Building-Neovim) for details.
diff --git a/cmake/GenerateHelptags.cmake.in b/cmake/GenerateHelptags.cmake.in
index 3ced2c0b17..db6f15b1be 100644
--- a/cmake/GenerateHelptags.cmake.in
+++ b/cmake/GenerateHelptags.cmake.in
@@ -8,7 +8,7 @@ else()
HELPTAGS_WORKING_DIRECTORY)
endif()
-message(STATUS "Generating helptags in ${HELPTAGS_WORKING_DIRECTORY}.")
+message(STATUS "Generating helptags in ${HELPTAGS_WORKING_DIRECTORY}")
if(EXISTS ${HELPTAGS_WORKING_DIRECTORY}/)
message(STATUS "${HELPTAGS_WORKING_DIRECTORY} already exists")
# If the doc directory already exists, helptags could fail due to duplicate
diff --git a/cmake/LuaHelpers.cmake b/cmake/LuaHelpers.cmake
index 3cba47412b..0239460f2b 100644
--- a/cmake/LuaHelpers.cmake
+++ b/cmake/LuaHelpers.cmake
@@ -17,7 +17,7 @@ endfunction()
function(check_lua_deps LUA_PRG_PATH MODULES RESULT_VAR)
# Check if the lua interpreter at the given path
# satisfies all Neovim dependencies
- message(STATUS "Checking Lua interpreter ${LUA_PRG_PATH}")
+ message(STATUS "Checking Lua interpreter: ${LUA_PRG_PATH}")
if(NOT EXISTS ${LUA_PRG_PATH})
message(STATUS
"[${LUA_PRG_PATH}] file not found")
diff --git a/cmake/RunLuacheck.cmake b/cmake/RunLuacheck.cmake
index 5129541cd8..4887e562a5 100644
--- a/cmake/RunLuacheck.cmake
+++ b/cmake/RunLuacheck.cmake
@@ -18,5 +18,5 @@ execute_process(
if(NOT res EQUAL 0)
message(STATUS "Output to stderr:\n${err}")
- message(FATAL_ERROR "Linting tests failed with error: ${res}.")
+ message(FATAL_ERROR "Linting tests failed with error: ${res}")
endif()
diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake
index a51990e925..a764b19850 100644
--- a/cmake/RunTests.cmake
+++ b/cmake/RunTests.cmake
@@ -50,5 +50,5 @@ file(REMOVE_RECURSE ${WORKING_DIR}/Xtest-tmpdir)
if(NOT res EQUAL 0)
message(STATUS "Output to stderr:\n${err}")
- message(FATAL_ERROR "Running ${TEST_TYPE} tests failed with error: ${res}.")
+ message(FATAL_ERROR "${TEST_TYPE} tests failed with error: ${res}")
endif()
diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim
index 8ca78f2782..21f2dfc58a 100644
--- a/runtime/autoload/man.vim
+++ b/runtime/autoload/man.vim
@@ -149,9 +149,9 @@ function! s:system(cmd, ...) abort
endfunction
function! s:get_page(path) abort
- " Disable hard-wrap by setting $MANWIDTH to a high value.
- " Use soft wrap instead (ftplugin/man.vim sets 'wrap', 'breakindent').
- let manwidth = 9999
+ " Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
+ " We use soft wrap: ftplugin/man.vim sets wrap/breakindent/….
+ let manwidth = 999
" Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db).
" http://comments.gmane.org/gmane.editors.vim.devel/29085
" Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
@@ -168,9 +168,10 @@ function! s:put_page(page) abort
silent keepjumps 1delete _
endwhile
" XXX: nroff justifies text by filling it with whitespace. That interacts
- " badly with our use of $MANWIDTH=9999. Hack around this by using a fixed
+ " badly with our use of $MANWIDTH=999. Hack around this by using a fixed
" size for those whitespace regions.
- silent! keeppatterns keepjumps %s/\s\{999,}/\=repeat(' ', 10)/g
+ silent! keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g
+ 1
lua require("man").highlight_man_page()
setlocal filetype=man
endfunction
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 064c08c190..2e1d89c524 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -9136,6 +9136,14 @@ This does NOT work: >
variables are automatically deleted when the function
ends.
+:unl[et] ${env-name} ... *:unlet-environment* *:unlet-$*
+ Remove environment variable {env-name}.
+ Can mix {name} and ${env-name} in one :unlet command.
+ No error message is given for a non-existing
+ variable, also without !.
+ If the system does not support deleting an environment
+ variable, it is made emtpy.
+
:lockv[ar][!] [depth] {name} ... *:lockvar* *:lockv*
Lock the internal variable {name}. Locking means that
it can no longer be changed (until it is unlocked).
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index dd9f9ad0f3..34c4db4047 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -358,6 +358,8 @@ argument.
instance a `nvim_get_api_info` call so that UI features can be
safely detected by the UI before attaching.
+ See |ui-startup| for more information about UI startup.
+
To embed nvim without using the UI protocol, `--headless` should
be supplied together with `--embed`. Then initialization is
performed without waiting for an UI. This is also equivalent
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index 6a73061aca..c3664ece18 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -4665,8 +4665,8 @@ cterm={attr-list} *attr-list* *highlight-cterm* *E418*
Note that "bold" can be used here and by using a bold font. They
have the same effect.
- If running in a terminal, "undercurl" acts as an alias for "underline".
- It is set using |highlight-guisp|.
+ "undercurl" falls back to "underline" in a terminal that does not
+ support it. The color is set using |highlight-guisp|.
start={term-list} *highlight-start* *E422*
stop={term-list} *term-list* *highlight-stop*
@@ -4799,7 +4799,8 @@ guifg={color-name} *highlight-guifg*
guibg={color-name} *highlight-guibg*
guisp={color-name} *highlight-guisp*
These give the foreground (guifg), background (guibg) and special
- (guisp) color to use in the GUI. "guisp" is used for undercurl.
+ (guisp) color to use in the GUI. "guisp" is used for undercurl
+ and underline.
There are a few special names:
NONE no color (transparent)
bg use normal background color
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 42ce7a5edf..a0f1b0770e 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -16,10 +16,12 @@ RPC API. The UI model consists of a terminal-like grid with a single,
monospace font size. Some elements (UI "widgets") can be drawn separately from
the grid ("externalized").
+
*ui-options*
-After connecting to Nvim (usually a spawned, embedded instance) use the
-|nvim_ui_attach()| API method to tell Nvim that your program wants to draw the
-Nvim screen grid with a size of width × height cells. `options` must be
+The |nvim_ui_attach()| API method is used to tell Nvim that your program wants to
+draw the Nvim screen grid with a size of width × height cells. This is typically
+done by an embedder, see |ui-startup| below for details, but an UI can also
+connect to a running nvim instance and invoke this method. `options` must be
a dictionary with these (optional) keys:
`rgb` Decides the color format. *ui-rgb*
Set true (default) for 24-bit RGB colors.
@@ -29,7 +31,7 @@ a dictionary with these (optional) keys:
`ext_tabline` Externalize the tabline. |ui-tabline|
`ext_cmdline` Externalize the cmdline. |ui-cmdline|
`ext_wildmenu` Externalize the wildmenu. |ui-wildmenu|
- `ext_newgrid` Use new revision of the grid events. |ui-newgrid|
+ `ext_linegrid` Use new revision of the grid events. |ui-linegrid|
`ext_hlstate` Use detailed highlight state. |ui-hlstate|
Specifying a non-existent option is an error. UIs can check the |api-metadata|
@@ -43,22 +45,26 @@ Nvim sends msgpack-rpc notifications to all attached UIs, with method name
Each update event is itself an array whose first element is the event name and
remaining elements are event-parameter tuples. This allows multiple events of
the same kind to be sent in a row without the event name being repeated. This
-batching is mostly used for "put", because each "put" event puts contents in
-one screen cell, but clients must be prepared for multiple argument sets being
-batched for all event kinds.
-
-Events must be handled in-order. The user should only see the updated screen
-state after all events in the same "redraw" batch are processed (not any
-intermediate state after processing only part of the array).
+batching is mostly used for "grid_line", because each "grid_line" event puts
+contents in one screen line, but clients must be prepared for multiple argument
+sets being batched for all event kinds.
+
+Events must be handled in-order. A "flush" event is sent when nvim is done
+redrawing the entire screen (so that all windows have a consistent view of
+buffer state, options etc). Clients should be prepared that several "redraw"
+batches are sent before the entire screen has been redrawn, and only the last
+batch will end in "flush". The user should only see the final state when
+"flush" is sent, and not any intermediate state after processing only part of
+the batch array, nor after a batch not ending with "flush".
By default, Nvim sends |ui-global| and |ui-grid-old| events; these suffice to
implement a terminal-like interface. However there are two revisions of the
-grid part of the protocol. The newer revision |ui-newgrid|, enabled by
-`ext_newgrid` option, has some improvements, such as a more efficient
-representation of highlighted text, simplified events and room for futher
-enhancements that will use multiple grids. The older revision is available and
-used by default only for backwards compatibility reasons. New UIs are strongly
-recommended to use |ui-newgrid|, as further protocol extensions will require it.
+grid part of the protocol. The newer revision |ui-linegrid|, enabled by
+`ext_linegrid` option, has a more effecient representation of text (especially
+highlighted text), and room for futher enhancements that will use
+multiple grids. The older revision is available and used by default only for
+backwards compatibility reasons. New UIs are strongly recommended to use
+|ui-linegrid|, as further protocol extensions will require it.
Nvim optionally sends screen elements "semantically" as structured events
instead of raw grid-lines, controlled by |ui-ext-options|. The UI must present
@@ -69,8 +75,44 @@ to existing update kinds. Clients must be prepared to ignore such extensions,
for forward-compatibility. |api-contract|
==============================================================================
+UI startup *ui-startup*
+
+Nvim defines a standard procedure for how an embedding UI should interact with
+the startup phase of Nvim. When spawning the nvim process, use the |--embed| flag
+but not the |--headless| flag. The started Nvim process will pause before loading
+startup files and reading buffers, and give the UI a chance to invoke requests
+to do early initialization. As soon as the UI invokes |nvim_ui_attach()|, the
+startup will continue.
+
+A simple UI only need to do a single |nvim_ui_attach()| request and then
+be prepared to handle any UI event. A more featureful UI, which might need
+additional configuration of the nvim process, should use the following startup
+procedure:
+
+1. Invoke |nvim_get_api_info()|, if this is needed to setup the client library
+ and/or to get the list of supported UI extensions.
+2. At this time, any configuration that should be happen before init.vim
+ loading should be done. Buffers and windows are not available at this
+ point, but this could be used to set |g:| variables visible to init.vim
+3. If the UI wants to do additional setup after the init.vim file was loaded
+ register an autocmd for VimEnter at this point: >
+
+ nvim_command("autocmd VimEnter * call rpcrequest(1, 'vimenter')")
+
+<4. Now invoke |nvim_ui_attach()|. The UI will need to handle keyboard input
+ at this point, as sourcing init.vim and loading buffers might lead to
+ blocking prompts.
+5. If step 3 was used, nvim will send a blocking "vimenter" request to the
+ UI. Inside this request handler, the UI can safely do any initialization
+ before entering normal mode, for instance reading variables set by
+ init.vim.
+
+==============================================================================
Global Events *ui-global*
+The following events will always be available, and describe global state of
+the editor.
+
["set_title", title]
["set_icon", icon]
Set the window title, and icon (minimized) window title, respectively.
@@ -144,15 +186,15 @@ Global Events *ui-global*
would conflict with other usages of the mouse. It is safe for a client
to ignore this and always send mouse events.
-["busy_on"]
-["busy_off"]
+["busy_start"]
+["busy_stop"]
Nvim started or stopped being busy, and possibly not responsive to
user input. This could be indicated to the user by hiding the cursor.
["suspend"]
- |:suspend| command or |CTRL-Z| mapping is used. A terminal client (or other
- client where it makes sense) could suspend itself. Other clients can
- safely ignore it.
+ |:suspend| command or |CTRL-Z| mapping is used. A terminal client (or
+ another client where it makes sense) could suspend itself. Other
+ clients can safely ignore it.
["update_menu"]
The menu mappings changed.
@@ -161,16 +203,24 @@ Global Events *ui-global*
["visual_bell"]
Notify the user with an audible or visual bell, respectively.
+["flush"]
+ Nvim is done redrawing the screen. For an implementation that renders
+ to an internal buffer, this is the time to display the redrawn parts
+ to the user.
+
==============================================================================
-Grid Events (new revision) *ui-newgrid*
+Grid Events (line-based) *ui-linegrid*
-These events are used if `ext_newgrid` option is set (recommended for all new
-UIs).
+These events are used if `ext_linegrid` option is set (recommended for all new
+UIs). The biggest change compared to previous revision is to use a single
+event `grid_line` to update the contents of a screen line (where the old
+protocol used a combination of cursor, highlight and text events)
Most of these events take a `grid` index as first parameter. Grid 1 is the
global grid used by default for the entire editor screen state. Grids other
-than that will be defined by future extensions. Just activating the `ext_newgrid`
-option by itself will never cause any additional grids to be created.
+than that will be defined by future extensions. Just activating the
+`ext_linegrid` option by itself will never cause any additional grids to be
+created.
Highlight attribute groups are predefined. UIs should maintain a table to map
numerical highlight `id`:s to the actual attributes.
@@ -299,10 +349,10 @@ numerical highlight `id`:s to the actual attributes.
from `set_scroll_region` which was end-inclusive.
==============================================================================
-Grid Events (first revision) *ui-grid-old*
+Legacy Grid Events (cell based) *ui-grid-old*
-This is an older representation of the screen grid, used if `ext_newgrid`
-option is not set.
+This is an older representation of the screen grid, used if `ext_linegrid`
+option is not set. New UIs should use |ui-linegrid|.
["resize", width, height]
The grid is resized to `width` and `height` cells.
@@ -389,9 +439,8 @@ option is not set.
==============================================================================
Detailed highlight state Extension *ui-hlstate*
-
Only sent if `ext_hlstate` option is set in |ui-options|. `ext_hlstate` implies
-`ext_newgrid`.
+`ext_linegrid`.
By default, nvim will only describe grid cells using the final calculated
higlight attributes, as described by the dict keys in |ui-event-highlight_set|.
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index bba76ffa97..de0d040b47 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -93,7 +93,7 @@ get_vim_sources() {
exit 1
fi
echo "Updating Vim sources: ${VIM_SOURCE_DIR}"
- git pull &&
+ git pull --ff &&
msg_ok "Updated Vim sources." ||
msg_err "Could not update Vim sources; ignoring error."
fi
@@ -156,8 +156,8 @@ preprocess_patch() {
# Remove "Last change ..." changes in doc files.
2>/dev/null $nvim --cmd 'set dir=/tmp' +'%s/^@@.*\n.*For Vim version.*Last change.*\n.*For Vim version.*Last change.*//' +w +q "$file"
- # Remove some testdir/Make_*.mak files
- local na_src_testdir='Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms'
+ # Remove screen dumps, testdir/Make_*.mak files
+ local na_src_testdir='Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms\|dumps/.*.dump'
2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('${na_src_testdir}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
# Remove version.c #7555
diff --git a/src/clint.py b/src/clint.py
index 9fd93ce143..34af5d15fd 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -49,6 +49,7 @@ from __future__ import unicode_literals
import codecs
import copy
+import fileinput
import getopt
import math # for log
import os
@@ -65,7 +66,7 @@ _USAGE = """
Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
[--counting=total|toplevel|detailed] [--root=subdir]
[--linelength=digits] [--record-errors=file]
- [--suppress-errors=file]
+ [--suppress-errors=file] [--stdin-filename=filename]
<file> [file] ...
The style guidelines this tries to follow are those in
@@ -167,6 +168,9 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
suppress-errors=file
Errors listed in the given file will not be reported.
+
+ stdin-filename=filename
+ Use specified filename when reading from stdin (file "-").
"""
# We categorize each error message we print. Here are the categories.
@@ -3456,10 +3460,12 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]):
# is processed.
if filename == '-':
- lines = codecs.StreamReaderWriter(sys.stdin,
- codecs.getreader('utf8'),
- codecs.getwriter('utf8'),
- 'replace').read().split('\n')
+ stdin = sys.stdin.read()
+ if sys.version_info < (3, 0):
+ stdin = stdin.decode('utf8')
+ lines = stdin.split('\n')
+ if _cpplint_state.stdin_filename is not None:
+ filename = _cpplint_state.stdin_filename
else:
lines = codecs.open(
filename, 'r', 'utf8', 'replace').read().split('\n')
@@ -3540,7 +3546,9 @@ def ParseArguments(args):
'linelength=',
'extensions=',
'record-errors=',
- 'suppress-errors='])
+ 'suppress-errors=',
+ 'stdin-filename=',
+ ])
except getopt.GetoptError:
PrintUsage('Invalid arguments.')
@@ -3550,6 +3558,7 @@ def ParseArguments(args):
counting_style = ''
record_errors_file = None
suppress_errors_file = None
+ stdin_filename = None
for (opt, val) in opts:
if opt == '--help':
@@ -3586,6 +3595,8 @@ def ParseArguments(args):
record_errors_file = val
elif opt == '--suppress-errors':
suppress_errors_file = val
+ elif opt == '--stdin-filename':
+ stdin_filename = val
if not filenames:
PrintUsage('No files were specified.')
@@ -3596,6 +3607,7 @@ def ParseArguments(args):
_SetCountingStyle(counting_style)
_SuppressErrorsFrom(suppress_errors_file)
_RecordErrorsTo(record_errors_file)
+ _cpplint_state.stdin_filename = stdin_filename
return filenames
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index b49de7abf3..01f8c9f71c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -127,7 +127,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height,
}
if (ui->ui_ext[kUIHlState]) {
- ui->ui_ext[kUINewgrid] = true;
+ ui->ui_ext[kUILinegrid] = true;
}
UIData *data = xmalloc(sizeof(UIData));
@@ -227,11 +227,11 @@ static void ui_set_option(UI *ui, bool init, String name, Object value,
return;
}
bool boolval = value.data.boolean;
- if (!init && i == kUINewgrid && boolval != ui->ui_ext[i]) {
+ if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
// There shouldn't be a reason for an UI to do this ever
// so explicitly don't support this.
api_set_error(error, kErrorTypeValidation,
- "ext_newgrid option cannot be changed");
+ "ext_linegrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
@@ -271,10 +271,10 @@ static void push_call(UI *ui, const char *name, Array args)
static void remote_ui_grid_clear(UI *ui, Integer grid)
{
Array args = ARRAY_DICT_INIT;
- if (ui->ui_ext[kUINewgrid]) {
+ if (ui->ui_ext[kUILinegrid]) {
ADD(args, INTEGER_OBJ(grid));
}
- const char *name = ui->ui_ext[kUINewgrid] ? "grid_clear" : "clear";
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_clear" : "clear";
push_call(ui, name, args);
}
@@ -282,12 +282,12 @@ static void remote_ui_grid_resize(UI *ui, Integer grid,
Integer width, Integer height)
{
Array args = ARRAY_DICT_INIT;
- if (ui->ui_ext[kUINewgrid]) {
+ if (ui->ui_ext[kUILinegrid]) {
ADD(args, INTEGER_OBJ(grid));
}
ADD(args, INTEGER_OBJ(width));
ADD(args, INTEGER_OBJ(height));
- const char *name = ui->ui_ext[kUINewgrid] ? "grid_resize" : "resize";
+ const char *name = ui->ui_ext[kUILinegrid] ? "grid_resize" : "resize";
push_call(ui, name, args);
}
@@ -295,7 +295,7 @@ static void remote_ui_grid_scroll(UI *ui, Integer grid, Integer top,
Integer bot, Integer left, Integer right,
Integer rows, Integer cols)
{
- if (ui->ui_ext[kUINewgrid]) {
+ if (ui->ui_ext[kUILinegrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(top));
@@ -341,7 +341,7 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
push_call(ui, "default_colors_set", args);
// Deprecated
- if (!ui->ui_ext[kUINewgrid]) {
+ if (!ui->ui_ext[kUILinegrid]) {
args = (Array)ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(ui->rgb ? rgb_fg : cterm_fg - 1));
push_call(ui, "update_fg", args);
@@ -359,7 +359,7 @@ static void remote_ui_default_colors_set(UI *ui, Integer rgb_fg,
static void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs,
HlAttrs cterm_attrs, Array info)
{
- if (!ui->ui_ext[kUINewgrid]) {
+ if (!ui->ui_ext[kUILinegrid]) {
return;
}
Array args = ARRAY_DICT_INIT;
@@ -397,7 +397,7 @@ static void remote_ui_highlight_set(UI *ui, int id)
static void remote_ui_grid_cursor_goto(UI *ui, Integer grid, Integer row,
Integer col)
{
- if (ui->ui_ext[kUINewgrid]) {
+ if (ui->ui_ext[kUILinegrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(row));
@@ -442,7 +442,7 @@ static void remote_ui_raw_line(UI *ui, Integer grid, Integer row,
const sattr_T *attrs)
{
UIData *data = ui->data;
- if (ui->ui_ext[kUINewgrid]) {
+ if (ui->ui_ext[kUILinegrid]) {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(grid));
ADD(args, INTEGER_OBJ(row));
@@ -508,9 +508,10 @@ static void remote_ui_flush(UI *ui)
{
UIData *data = ui->data;
if (data->buffer.size > 0) {
- if (!ui->ui_ext[kUINewgrid]) {
+ if (!ui->ui_ext[kUILinegrid]) {
remote_ui_cursor_goto(ui, data->cursor_row, data->cursor_col);
}
+ push_call(ui, "flush", (Array)ARRAY_DICT_INIT);
rpc_send_event(data->channel_id, "redraw", data->buffer);
data->buffer = (Array)ARRAY_DICT_INIT;
}
@@ -549,7 +550,7 @@ static Array translate_firstarg(UI *ui, Array args)
static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed)
{
- if (!ui->ui_ext[kUINewgrid]) {
+ if (!ui->ui_ext[kUILinegrid]) {
// the representation of highlights in cmdline changed, translate back
// never consumes args
if (strequal(name, "cmdline_show")) {
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index 456ad0c8cc..9ddf788376 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -63,7 +63,7 @@ void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
void scroll(Integer count)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-// Second revison of the grid protocol, used with ext_newgrid ui option
+// Second revison of the grid protocol, used with ext_linegrid ui option
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index a28d9774ab..68f6ff303b 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -4533,9 +4533,10 @@ do_arg_all (
use_firstwin = true;
}
- for (i = 0; i < count && i < opened_len && !got_int; ++i) {
- if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1)
- arg_had_last = TRUE;
+ for (i = 0; i < count && i < opened_len && !got_int; i++) {
+ if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) {
+ arg_had_last = true;
+ }
if (opened[i] > 0) {
/* Move the already present window to below the current window */
if (curwin->w_arg_idx != i) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 2c12534b21..9c678168bb 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -2818,6 +2818,18 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
lval_T lv;
do {
+ if (*arg == '$') {
+ const char *name = (char *)++arg;
+
+ if (get_env_len((const char_u **)&arg) == 0) {
+ EMSG2(_(e_invarg2), name - 1);
+ return;
+ }
+ os_unsetenv(name);
+ arg = skipwhite(arg);
+ continue;
+ }
+
// Parse the name and find the end.
char_u *const name_end = (char_u *)get_lval(arg, NULL, &lv, true,
eap->skip || error,
@@ -14664,6 +14676,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
static char *e_invact = N_("E927: Invalid action: '%s'");
const char *title = NULL;
int action = ' ';
+ static int recursive = 0;
rettv->vval.v_number = -1;
dict_T *d = NULL;
@@ -14671,6 +14684,9 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
if (list_arg->v_type != VAR_LIST) {
EMSG(_(e_listreq));
return;
+ } else if (recursive != 0) {
+ EMSG(_(e_au_recursive));
+ return;
}
typval_T *action_arg = &args[1];
@@ -14712,10 +14728,12 @@ skip_args:
title = (wp ? "setloclist()" : "setqflist()");
}
+ recursive++;
list_T *const l = list_arg->vval.v_list;
if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
rettv->vval.v_number = 0;
}
+ recursive--;
}
/*
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 328f46443f..e72bb7b870 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -222,7 +222,7 @@ return {
pathshorten={args=1},
pow={args=2},
prevnonblank={args=1},
- printf={args=varargs(2)},
+ printf={args=varargs(1)},
pumvisible={},
py3eval={args=1},
pyeval={args=1},
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index aaa4dbdfb7..a091862e42 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -424,6 +424,7 @@ void ex_sort(exarg_T *eap)
sort_abort = sort_ic = sort_rx = sort_nr = sort_flt = 0;
size_t format_found = 0;
+ bool change_occurred = false; // Buffer contents changed.
for (p = eap->arg; *p != NUL; ++p) {
if (ascii_iswhite(*p)) {
@@ -584,8 +585,16 @@ void ex_sort(exarg_T *eap)
// Insert the lines in the sorted order below the last one.
lnum = eap->line2;
- for (i = 0; i < count; ++i) {
- s = ml_get(nrs[eap->forceit ? count - i - 1 : i].lnum);
+ for (i = 0; i < count; i++) {
+ const linenr_T get_lnum = nrs[eap->forceit ? count - i - 1 : i].lnum;
+
+ // If the original line number of the line being placed is not the same
+ // as "lnum" (accounting for offset), we know that the buffer changed.
+ if (get_lnum + ((linenr_T)count - 1) != lnum) {
+ change_occurred = true;
+ }
+
+ s = ml_get(get_lnum);
if (!unique || i == 0
|| (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) {
// Copy the line into a buffer, it may become invalid in
@@ -614,10 +623,13 @@ void ex_sort(exarg_T *eap)
if (deleted > 0) {
mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted,
false);
+ msgmore(-deleted);
} else if (deleted < 0) {
mark_adjust(eap->line2, MAXLNUM, -deleted, 0L, false);
}
- changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
+ if (change_occurred || deleted != 0) {
+ changed_lines(eap->line1, 0, eap->line2 + 1, -deleted, true);
+ }
curwin->w_cursor.lnum = eap->line1;
beginline(BL_WHITE | BL_FIX);
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 6e695a8897..90fb7b8bc3 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1285,7 +1285,6 @@ void dialog_changed(buf_T *buf, bool checkall)
int ret;
exarg_T ea;
- assert(buf->b_fname != NULL);
dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname);
if (checkall) {
ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1);
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index ad51de46ee..2a733f5831 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -3260,8 +3260,15 @@ const char * set_one_cmd_context(
while ((xp->xp_pattern = (char_u *)strchr(arg, ' ')) != NULL) {
arg = (const char *)xp->xp_pattern + 1;
}
+
xp->xp_context = EXPAND_USER_VARS;
xp->xp_pattern = (char_u *)arg;
+
+ if (*xp->xp_pattern == '$') {
+ xp->xp_context = EXPAND_ENV_VARS;
+ xp->xp_pattern++;
+ }
+
break;
case CMD_function:
@@ -6536,6 +6543,13 @@ void alist_expand(int *fnum_list, int fnum_len)
void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum_list, int fnum_len)
{
int i;
+ static int recursive = 0;
+
+ if (recursive) {
+ EMSG(_(e_au_recursive));
+ return;
+ }
+ recursive++;
alist_clear(al);
ga_grow(&al->al_ga, count);
@@ -6560,8 +6574,10 @@ void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum
xfree(files);
}
- if (al == &global_alist)
- arg_had_last = FALSE;
+ if (al == &global_alist) {
+ arg_had_last = false;
+ }
+ recursive--;
}
/*
@@ -9040,8 +9056,10 @@ makeopens(
// cursor can be set. This is done again below.
// winminheight and winminwidth need to be set to avoid an error if the
// user has set winheight or winwidth.
- if (put_line(fd, "set winminheight=1 winminwidth=1 winheight=1 winwidth=1")
- == FAIL) {
+ if (put_line(fd, "set winminheight=0") == FAIL
+ || put_line(fd, "set winheight=1") == FAIL
+ || put_line(fd, "set winminwidth=0") == FAIL
+ || put_line(fd, "set winwidth=1") == FAIL) {
return FAIL;
}
if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 4983484100..1ec00b1e25 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -793,9 +793,11 @@ static int command_line_execute(VimState *state, int key)
no_mapping--;
// CTRL-\ e doesn't work when obtaining an expression, unless it
// is in a mapping.
- if (s->c != Ctrl_N && s->c != Ctrl_G && (s->c != 'e'
- || (ccline.cmdfirstc == '='
- && KeyTyped))) {
+ if (s->c != Ctrl_N
+ && s->c != Ctrl_G
+ && (s->c != 'e'
+ || (ccline.cmdfirstc == '=' && KeyTyped)
+ || cmdline_star > 0)) {
vungetc(s->c);
s->c = Ctrl_BSL;
} else if (s->c == 'e') {
@@ -1350,7 +1352,8 @@ static int command_line_handle_key(CommandLineState *s)
// a new one...
new_cmdpos = -1;
if (s->c == '=') {
- if (ccline.cmdfirstc == '=') { // can't do this recursively
+ if (ccline.cmdfirstc == '=' // can't do this recursively
+ || cmdline_star > 0) { // or when typing a password
beep_flush();
s->c = ESC;
} else {
@@ -5587,6 +5590,9 @@ static struct cmdline_info *get_ccline_ptr(void)
*/
char_u *get_cmdline_str(void)
{
+ if (cmdline_star > 0) {
+ return NULL;
+ }
struct cmdline_info *p = get_ccline_ptr();
if (p == NULL)
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 9bba2379cd..2dc8073b1e 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -528,8 +528,8 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
*/
EXTERN alist_T global_alist; /* global argument list */
EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id
-EXTERN int arg_had_last INIT(= FALSE); /* accessed last file in
- global_alist */
+EXTERN bool arg_had_last INIT(= false); // accessed last file in
+ // global_alist
EXTERN int ru_col; /* column for ruler */
EXTERN int ru_wid; /* 'rulerfmt' width of ruler when non-zero */
@@ -1099,6 +1099,8 @@ EXTERN char_u e_notset[] INIT(= N_("E764: Option '%s' is not set"));
EXTERN char_u e_invalidreg[] INIT(= N_("E850: Invalid register name"));
EXTERN char_u e_dirnotf[] INIT(= N_(
"E919: Directory not found in '%s': \"%s\""));
+EXTERN char_u e_au_recursive[] INIT(= N_(
+ "E952: Autocommand caused recursive behavior"));
EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long"));
EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String"));
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 503c4b122c..4d912c452b 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -109,6 +109,12 @@ bool logmsg(int log_level, const char *context, const char *func_name,
return false;
}
+#ifdef EXITFREE
+ // Logging after we've already started freeing all our memory will only cause
+ // pain. We need access to VV_PROGPATH, homedir, etc.
+ assert(!entered_free_all_mem);
+#endif
+
log_lock();
bool ret = false;
FILE *log_file = open_log_file();
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 41e8c42803..af54e62393 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -258,6 +258,14 @@ int main(int argc, char **argv)
// Process the command line arguments. File names are put in the global
// argument list "global_alist".
command_line_scan(&params);
+
+ if (embedded_mode) {
+ const char *err;
+ if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) {
+ abort();
+ }
+ }
+
server_init(params.listen_addr);
if (GARGCOUNT > 0) {
@@ -848,10 +856,6 @@ static void command_line_scan(mparm_T *parmp)
headless_mode = true;
} else if (STRICMP(argv[0] + argv_idx, "embed") == 0) {
embedded_mode = true;
- const char *err;
- if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) {
- abort();
- }
} else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) {
want_argument = true;
argv_idx += 6;
@@ -1624,9 +1628,10 @@ static void edit_buffers(mparm_T *parmp, char_u *cwd)
win_close(curwin, true);
advance = false;
}
- if (arg_idx == GARGCOUNT - 1)
- arg_had_last = TRUE;
- ++arg_idx;
+ if (arg_idx == GARGCOUNT - 1) {
+ arg_had_last = true;
+ }
+ arg_idx++;
}
os_breakcheck();
if (got_int) {
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 5602a29f50..ec0238e7c9 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -535,6 +535,7 @@ void ml_open_file(buf_T *buf)
void check_need_swap(int newfile)
{
int old_msg_silent = msg_silent; // might be reset by an E325 message
+ msg_silent = 0; // If swap dialog prompts for input, user needs to see it!
if (curbuf->b_may_swap && (!curbuf->b_p_ro || !newfile)) {
ml_open_file(curbuf);
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 4a2874abeb..442e5d6dff 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -98,6 +98,11 @@ static void comp_botline(win_T *wp)
static linenr_T last_cursorline = 0;
+void reset_cursorline(void)
+{
+ last_cursorline = 0;
+}
+
// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
static void redraw_for_cursorline(win_T *wp)
{
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 45620bfc54..f87de52a82 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1269,7 +1269,8 @@ static void normal_redraw(NormalState *s)
xfree(p);
}
- if (need_fileinfo) { // show file info after redraw
+ // show fileinfo after redraw
+ if (need_fileinfo && !shortmess(SHM_FILEINFO)) {
fileinfo(false, true, false);
need_fileinfo = false;
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 7cda42ef20..eb2780ce7a 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -2434,7 +2434,7 @@ did_set_string_option (
int did_chartab = FALSE;
char_u **gvarp;
bool free_oldval = (options[opt_idx].flags & P_ALLOCED);
- int ft_changed = false;
+ bool value_changed = false;
/* Get the global option to compare with, otherwise we would have to check
* two values for all local options. */
@@ -3155,11 +3155,13 @@ did_set_string_option (
if (!valid_filetype(*varp)) {
errmsg = e_invarg;
} else {
- ft_changed = STRCMP(oldval, *varp) != 0;
+ value_changed = STRCMP(oldval, *varp) != 0;
}
} else if (gvarp == &p_syn) {
if (!valid_filetype(*varp)) {
errmsg = e_invarg;
+ } else {
+ value_changed = STRCMP(oldval, *varp) != 0;
}
} else if (varp == &curwin->w_p_winhl) {
if (!parse_winhl_opt(curwin)) {
@@ -3235,14 +3237,28 @@ did_set_string_option (
*/
/* When 'syntax' is set, load the syntax of that name */
if (varp == &(curbuf->b_p_syn)) {
- apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
- curbuf->b_fname, TRUE, curbuf);
+ static int syn_recursive = 0;
+
+ syn_recursive++;
+ // Only pass true for "force" when the value changed or not used
+ // recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname,
+ value_changed || syn_recursive == 1, curbuf);
+ syn_recursive--;
} else if (varp == &(curbuf->b_p_ft)) {
// 'filetype' is set, trigger the FileType autocommand
- if (!(opt_flags & OPT_MODELINE) || ft_changed) {
+ // Skip this when called from a modeline and the filetype was
+ // already set to this value.
+ if (!(opt_flags & OPT_MODELINE) || value_changed) {
+ static int ft_recursive = 0;
+
+ ft_recursive++;
did_filetype = true;
- apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft,
- curbuf->b_fname, true, curbuf);
+ // Only pass true for "force" when the value changed or not
+ // used recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, curbuf->b_fname,
+ value_changed || ft_recursive == 1, curbuf);
+ ft_recursive--;
// Just in case the old "curbuf" is now invalid
if (varp != &(curbuf->b_p_ft)) {
varp = NULL;
@@ -3705,6 +3721,9 @@ static char *set_bool_option(const int opt_idx, char_u *const varp,
} else if ((int *)varp == &p_lnr) {
// 'langnoremap' -> !'langremap'
p_lrm = !p_lnr;
+ } else if ((int *)varp == &curwin->w_p_cul && !value && old_value) {
+ // 'cursorline'
+ reset_cursorline();
// 'undofile'
} else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) {
// Only take action when the option was set. When reset we do not
diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c
index 27660712da..09ba718302 100644
--- a/src/nvim/os_unix.c
+++ b/src/nvim/os_unix.c
@@ -149,11 +149,12 @@ void mch_exit(int r)
stream_set_blocking(input_global_fd(), true); // normalize stream (#2598)
}
+ ILOG("Nvim exit: %d", r);
+
#ifdef EXITFREE
free_all_mem();
#endif
- ILOG("Nvim exit: %d", r);
exit(r);
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index fe6a15c5fc..ec4b31c40d 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -4588,14 +4588,18 @@ void status_redraw_all(void)
}
}
-/*
- * mark all status lines of the current buffer for redraw
- */
+/// Marks all status lines of the current buffer for redraw.
void status_redraw_curbuf(void)
{
+ status_redraw_buf(curbuf);
+}
+
+/// Marks all status lines of the specified buffer for redraw.
+void status_redraw_buf(buf_T *buf)
+{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_status_height != 0 && wp->w_buffer == curbuf) {
- wp->w_redr_status = TRUE;
+ if (wp->w_status_height != 0 && wp->w_buffer == buf) {
+ wp->w_redr_status = true;
redraw_later(VALID);
}
}
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 4c054dc8e0..e0e1897b88 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -6884,7 +6884,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// "fg", which have been changed now.
highlight_attr_set_all();
- if (!ui_is_external(kUINewgrid) && starting == 0) {
+ if (!ui_is_external(kUILinegrid) && starting == 0) {
// Older UIs assume that we clear the screen after normal group is
// changed
ui_refresh();
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index d83986cb15..d831979022 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -668,6 +668,7 @@ static void buf_set_term_title(buf_T *buf, char *title)
false,
&err);
api_clear_error(&err);
+ status_redraw_buf(buf);
}
static int term_settermprop(VTermProp prop, VTermValue *val, void *data)
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index f10163e351..36dcdc3386 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -38,6 +38,7 @@ source test_sort.vim
source test_source_utf8.vim
source test_sha256.vim
source test_statusline.vim
+source test_suspend.vim
source test_syn_attr.vim
source test_tabline.vim
" source test_tabpage.vim
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index ad967c528c..aaf32dff04 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -166,6 +166,9 @@ function Test_printf_spec_s()
endfunc
function Test_printf_misc()
+ call assert_equal('123', printf('123'))
+ call assert_fails("call printf('123', 3)", "E767:")
+
call assert_equal('123', printf('%d', 123))
call assert_equal('123', printf('%i', 123))
call assert_equal('123', printf('%D', 123))
diff --git a/src/nvim/testdir/test_getcwd.vim b/src/nvim/testdir/test_getcwd.vim
index 15eab2abbb..194963f694 100644
--- a/src/nvim/testdir/test_getcwd.vim
+++ b/src/nvim/testdir/test_getcwd.vim
@@ -89,3 +89,15 @@ function Test_GetCwd()
call assert_equal("y Xdir2 1", GetCwdInfo(2, tp_nr))
call assert_equal("z Xdir3 1", GetCwdInfo(1, tp_nr))
endfunc
+
+function Test_GetCwd_lcd_shellslash()
+ new
+ let root = fnamemodify('/', ':p')
+ exe 'lcd '.root
+ let cwd = getcwd()
+ if !exists('+shellslash') || &shellslash
+ call assert_equal(cwd[-1:], '/')
+ else
+ call assert_equal(cwd[-1:], '\')
+ endif
+endfunc
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index 023332c90a..999d4dbd4a 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -106,6 +106,43 @@ func Test_list_range_assign()
call assert_equal([1, 2], l)
endfunc
+" Test removing items in list
+func Test_list_func_remove()
+ " Test removing 1 element
+ let l = [1, 2, 3, 4]
+ call assert_equal(1, remove(l, 0))
+ call assert_equal([2, 3, 4], l)
+
+ let l = [1, 2, 3, 4]
+ call assert_equal(2, remove(l, 1))
+ call assert_equal([1, 3, 4], l)
+
+ let l = [1, 2, 3, 4]
+ call assert_equal(4, remove(l, -1))
+ call assert_equal([1, 2, 3], l)
+
+ " Test removing range of element(s)
+ let l = [1, 2, 3, 4]
+ call assert_equal([3], remove(l, 2, 2))
+ call assert_equal([1, 2, 4], l)
+
+ let l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, 1, 2))
+ call assert_equal([1, 4], l)
+
+ let l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, -3, -2))
+ call assert_equal([1, 4], l)
+
+ " Test invalid cases
+ let l = [1, 2, 3, 4]
+ call assert_fails("call remove(l, 5)", 'E684:')
+ call assert_fails("call remove(l, 1, 5)", 'E684:')
+ call assert_fails("call remove(l, 3, 2)", 'E16:')
+ call assert_fails("call remove(1, 0)", 'E712:')
+ call assert_fails("call remove(l, l)", 'E745:')
+endfunc
+
" Tests for Dictionary type
func Test_dict()
@@ -222,6 +259,17 @@ func Test_script_local_dict_func()
unlet g:dict
endfunc
+" Test removing items in la dictionary
+func Test_dict_func_remove()
+ let d = {1:'a', 2:'b', 3:'c'}
+ call assert_equal('b', remove(d, 2))
+ call assert_equal({1:'a', 3:'c'}, d)
+
+ call assert_fails("call remove(d, 1, 2)", 'E118:')
+ call assert_fails("call remove(d, 'a')", 'E716:')
+ call assert_fails("call remove(d, [])", 'E730:')
+endfunc
+
" Nasty: remove func from Dict that's being called (works)
func Test_dict_func_remove_in_use()
let d = {1:1}
diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim
index 4fddb47b58..14d008a17f 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/src/nvim/testdir/test_sort.vim
@@ -1,13 +1,13 @@
-" Test sort()
+" Tests for the "sort()" function and for the ":sort" command.
-:func Compare1(a, b) abort
+func Compare1(a, b) abort
call sort(range(3), 'Compare2')
return a:a - a:b
-:endfunc
+endfunc
-:func Compare2(a, b) abort
+func Compare2(a, b) abort
return a:a - a:b
-:endfunc
+endfunc
func Test_sort_strings()
" numbers compared as strings
@@ -45,7 +45,7 @@ func Test_sort_default()
call assert_fails('call sort([3.3, 1, "2"], 3)', "E474")
endfunc
-" Tests for the :sort command
+" Tests for the ":sort" command.
func Test_sort_cmd()
let tests = [
\ {
@@ -1167,18 +1167,87 @@ func Test_sort_cmd()
\ '1.234',
\ '123.456'
\ ]
- \ }
+ \ },
+ \ {
+ \ 'name' : 'alphabetical, sorted input',
+ \ 'cmd' : 'sort',
+ \ 'input' : [
+ \ 'a',
+ \ 'b',
+ \ 'c',
+ \ ],
+ \ 'expected' : [
+ \ 'a',
+ \ 'b',
+ \ 'c',
+ \ ]
+ \ },
+ \ {
+ \ 'name' : 'alphabetical, sorted input, unique at end',
+ \ 'cmd' : 'sort u',
+ \ 'input' : [
+ \ 'aa',
+ \ 'bb',
+ \ 'cc',
+ \ 'cc',
+ \ ],
+ \ 'expected' : [
+ \ 'aa',
+ \ 'bb',
+ \ 'cc',
+ \ ]
+ \ },
\ ]
for t in tests
enew!
call append(0, t.input)
$delete _
- exe t.cmd
+ setlocal nomodified
+ execute t.cmd
+
call assert_equal(t.expected, getline(1, '$'), t.name)
+
+ " Previously, the ":sort" command would set 'modified' even if the buffer
+ " contents did not change. Here, we check that this problem is fixed.
+ if t.input == t.expected
+ call assert_false(&modified, t.name . ': &mod is not correct')
+ else
+ call assert_true(&modified, t.name . ': &mod is not correct')
+ endif
endfor
call assert_fails('sort no', 'E474')
enew!
endfunc
+
+func Test_sort_cmd_report()
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setlocal nomodified
+ let res = execute('%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ call assert_match("6 fewer lines", res)
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setlocal nomodified report=10
+ let res = execute('%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ call assert_equal("", res)
+ enew!
+ call append(0, repeat([1], 3) + repeat([2], 3) + repeat([3], 3))
+ $delete _
+ setl report&vim
+ setlocal nomodified
+ let res = execute('1g/^/%sort u')
+
+ call assert_equal([1,2,3], map(getline(1, '$'), 'v:val+0'))
+ " the output comes from the :g command, not from the :sort
+ call assert_match("6 fewer lines", res)
+ enew!
+ endfunc
diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim
new file mode 100644
index 0000000000..462173e8cc
--- /dev/null
+++ b/src/nvim/testdir/test_suspend.vim
@@ -0,0 +1,51 @@
+" Test :suspend
+
+source shared.vim
+
+func Test_suspend()
+ if !has('terminal') || !executable('/bin/sh')
+ return
+ endif
+
+ let buf = term_start('/bin/sh')
+ " Wait for shell prompt.
+ call WaitForAssert({-> assert_match('$ $', term_getline(buf, '.'))})
+
+ call term_sendkeys(buf, v:progpath
+ \ . " --clean -X"
+ \ . " -c 'set nu'"
+ \ . " -c 'call setline(1, \"foo\")'"
+ \ . " Xfoo\<CR>")
+ " Cursor in terminal buffer should be on first line in spawned vim.
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+
+ for suspend_cmd in [":suspend\<CR>",
+ \ ":stop\<CR>",
+ \ ":suspend!\<CR>",
+ \ ":stop!\<CR>",
+ \ "\<C-Z>"]
+ " Suspend and wait for shell prompt.
+ call term_sendkeys(buf, suspend_cmd)
+ call WaitForAssert({-> assert_match('$ $', term_getline(buf, '.'))})
+
+ " Without 'autowrite', buffer should not be written.
+ call assert_equal(0, filereadable('Xfoo'))
+
+ call term_sendkeys(buf, "fg\<CR>")
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+ endfor
+
+ " Test that :suspend! with 'autowrite' writes content of buffers if modified.
+ call term_sendkeys(buf, ":set autowrite\<CR>")
+ call assert_equal(0, filereadable('Xfoo'))
+ call term_sendkeys(buf, ":suspend\<CR>")
+ " Wait for shell prompt.
+ call WaitForAssert({-> assert_match('$ $', term_getline(buf, '.'))})
+ call assert_equal(['foo'], readfile('Xfoo'))
+ call term_sendkeys(buf, "fg\<CR>")
+ call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))})
+
+ exe buf . 'bwipe!'
+ call delete('Xfoo')
+ set autowrite&
+endfunc
diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim
index 3f06058d03..b02bdaab3b 100644
--- a/src/nvim/testdir/test_unlet.vim
+++ b/src/nvim/testdir/test_unlet.vim
@@ -28,3 +28,37 @@ endfunc
func Test_unlet_fails()
call assert_fails('unlet v:["count"]', 'E46:')
endfunc
+
+func Test_unlet_env()
+ let envcmd = has('win32') ? 'set' : 'env'
+
+ let $FOOBAR = 'test'
+ let found = 0
+ for kv in split(system(envcmd), "\r*\n")
+ if kv == 'FOOBAR=test'
+ let found = 1
+ endif
+ endfor
+ call assert_equal(1, found)
+
+ unlet $FOOBAR
+ let found = 0
+ for kv in split(system(envcmd), "\r*\n")
+ if kv == 'FOOBAR=test'
+ let found = 1
+ endif
+ endfor
+ call assert_equal(0, found)
+
+ unlet $MUST_NOT_BE_AN_ERROR
+endfunc
+
+func Test_unlet_complete()
+ let g:FOOBAR = 1
+ call feedkeys(":unlet g:FOO\t\n", 'tx')
+ call assert_true(!exists('g:FOOBAR'))
+
+ let $FOOBAR = 1
+ call feedkeys(":unlet $FOO\t\n", 'tx')
+ call assert_true(!exists('$FOOBAR') || empty($FOOBAR))
+endfunc
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 713fe6a2e5..3ed0fe0cd6 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -118,6 +118,7 @@ typedef struct {
int resize_screen;
int reset_scroll_region;
int set_cursor_style, reset_cursor_style;
+ int enter_undercurl_mode, exit_undercurl_mode, set_underline_color;
} unibi_ext;
} TUIData;
@@ -156,7 +157,7 @@ UI *tui_start(void)
ui->raw_line = tui_raw_line;
memset(ui->ui_ext, 0, sizeof(ui->ui_ext));
- ui->ui_ext[kUINewgrid] = true;
+ ui->ui_ext[kUILinegrid] = true;
return ui_bridge_attach(ui, tui_main, tui_scheduler);
}
@@ -438,15 +439,16 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data)
static bool attrs_differ(HlAttrs a1, HlAttrs a2, bool rgb)
{
if (rgb) {
- // TODO(bfredl): when we start to support special color,
- // rgb_sp_color must be added here
return a1.rgb_fg_color != a2.rgb_fg_color
|| a1.rgb_bg_color != a2.rgb_bg_color
- || a1.rgb_ae_attr != a2.rgb_ae_attr;
+ || a1.rgb_ae_attr != a2.rgb_ae_attr
+ || a1.rgb_sp_color != a2.rgb_sp_color;
} else {
return a1.cterm_fg_color != a2.cterm_fg_color
|| a1.cterm_bg_color != a2.cterm_bg_color
- || a1.cterm_ae_attr != a2.cterm_ae_attr;
+ || a1.cterm_ae_attr != a2.cterm_ae_attr
+ || (a1.cterm_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)
+ && a1.rgb_sp_color != a2.rgb_sp_color);
}
}
@@ -483,12 +485,21 @@ static void update_attrs(UI *ui, HlAttrs attrs)
bool italic = attr & HL_ITALIC;
bool reverse = attr & HL_INVERSE;
bool standout = attr & HL_STANDOUT;
- bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL);
+
+ bool underline;
+ bool undercurl;
+ if (data->unibi_ext.enter_undercurl_mode) {
+ underline = attr & HL_UNDERLINE;
+ undercurl = attr & HL_UNDERCURL;
+ } else {
+ underline = (attr & HL_UNDERLINE) || (attr & HL_UNDERCURL);
+ undercurl = false;
+ }
if (unibi_get_str(data->ut, unibi_set_attributes)) {
- if (bold || reverse || underline || undercurl || standout) {
+ if (bold || reverse || underline || standout) {
UNIBI_SET_NUM_VAR(data->params[0], standout);
- UNIBI_SET_NUM_VAR(data->params[1], underline || undercurl);
+ UNIBI_SET_NUM_VAR(data->params[1], underline);
UNIBI_SET_NUM_VAR(data->params[2], reverse);
UNIBI_SET_NUM_VAR(data->params[3], 0); // blink
UNIBI_SET_NUM_VAR(data->params[4], 0); // dim
@@ -507,7 +518,7 @@ static void update_attrs(UI *ui, HlAttrs attrs)
if (bold) {
unibi_out(ui, unibi_enter_bold_mode);
}
- if (underline || undercurl) {
+ if (underline) {
unibi_out(ui, unibi_enter_underline_mode);
}
if (standout) {
@@ -520,6 +531,18 @@ static void update_attrs(UI *ui, HlAttrs attrs)
if (italic) {
unibi_out(ui, unibi_enter_italics_mode);
}
+ if (undercurl && data->unibi_ext.enter_undercurl_mode) {
+ unibi_out_ext(ui, data->unibi_ext.enter_undercurl_mode);
+ }
+ if ((undercurl || underline) && data->unibi_ext.set_underline_color) {
+ int color = attrs.rgb_sp_color;
+ if (color != -1) {
+ UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red
+ UNIBI_SET_NUM_VAR(data->params[1], (color >> 8) & 0xff); // green
+ UNIBI_SET_NUM_VAR(data->params[2], color & 0xff); // blue
+ unibi_out_ext(ui, data->unibi_ext.set_underline_color);
+ }
+ }
if (ui->rgb) {
if (fg != -1) {
UNIBI_SET_NUM_VAR(data->params[0], (fg >> 16) & 0xff); // red
@@ -573,7 +596,7 @@ static void print_cell(UI *ui, UCell *ptr)
// Printing the next character finally advances the cursor.
final_column_wrap(ui);
}
- update_attrs(ui, ptr->attrs);
+ update_attrs(ui, kv_A(data->attrs, ptr->attr));
out(ui, ptr->data, strlen(ptr->data));
grid->col++;
if (data->immediate_wrap_after_last_column) {
@@ -589,7 +612,8 @@ static bool cheap_to_print(UI *ui, int row, int col, int next)
UCell *cell = grid->cells[row] + col;
while (next) {
next--;
- if (attrs_differ(cell->attrs, data->print_attrs, ui->rgb)) {
+ if (attrs_differ(kv_A(data->attrs, cell->attr),
+ data->print_attrs, ui->rgb)) {
if (data->default_attr) {
return false;
}
@@ -763,43 +787,31 @@ static void clear_region(UI *ui, int top, int bot, int left, int right,
cursor_goto(ui, data->row, data->col);
}
-static bool can_use_scroll(UI * ui)
+static void set_scroll_region(UI *ui, int top, int bot, int left, int right)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
- return data->scroll_region_is_full_screen
- || (data->can_change_scroll_region
- && ((grid->left == 0 && grid->right == ui->width - 1)
- || data->can_set_lr_margin
- || data->can_set_left_right_margin));
-}
-
-static void set_scroll_region(UI *ui)
-{
- TUIData *data = ui->data;
- UGrid *grid = &data->grid;
-
- UNIBI_SET_NUM_VAR(data->params[0], grid->top);
- UNIBI_SET_NUM_VAR(data->params[1], grid->bot);
+ UNIBI_SET_NUM_VAR(data->params[0], top);
+ UNIBI_SET_NUM_VAR(data->params[1], bot);
unibi_out(ui, unibi_change_scroll_region);
- if (grid->left != 0 || grid->right != ui->width - 1) {
+ if (left != 0 || right != ui->width - 1) {
unibi_out_ext(ui, data->unibi_ext.enable_lr_margin);
if (data->can_set_lr_margin) {
- UNIBI_SET_NUM_VAR(data->params[0], grid->left);
- UNIBI_SET_NUM_VAR(data->params[1], grid->right);
+ UNIBI_SET_NUM_VAR(data->params[0], left);
+ UNIBI_SET_NUM_VAR(data->params[1], right);
unibi_out(ui, unibi_set_lr_margin);
} else {
- UNIBI_SET_NUM_VAR(data->params[0], grid->left);
+ UNIBI_SET_NUM_VAR(data->params[0], left);
unibi_out(ui, unibi_set_left_margin_parm);
- UNIBI_SET_NUM_VAR(data->params[0], grid->right);
+ UNIBI_SET_NUM_VAR(data->params[0], right);
unibi_out(ui, unibi_set_right_margin_parm);
}
}
unibi_goto(ui, grid->row, grid->col);
}
-static void reset_scroll_region(UI *ui)
+static void reset_scroll_region(UI *ui, bool fullwidth)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
@@ -811,7 +823,7 @@ static void reset_scroll_region(UI *ui)
UNIBI_SET_NUM_VAR(data->params[1], ui->height - 1);
unibi_out(ui, unibi_change_scroll_region);
}
- if (grid->left != 0 || grid->right != ui->width - 1) {
+ if (!fullwidth) {
if (data->can_set_lr_margin) {
UNIBI_SET_NUM_VAR(data->params[0], 0);
UNIBI_SET_NUM_VAR(data->params[1], ui->width - 1);
@@ -848,7 +860,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height)
unibi_out_ext(ui, data->unibi_ext.resize_screen);
// DECSLPP does not reset the scroll region.
if (data->scroll_region_is_full_screen) {
- reset_scroll_region(ui);
+ reset_scroll_region(ui, ui->width == grid->width);
}
} else { // Already handled the SIGWINCH signal; avoid double-resize.
got_winch = false;
@@ -1006,28 +1018,35 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
data->showing_mode = (ModeShape)mode_idx;
}
-static void tui_grid_scroll(UI *ui, Integer g, Integer top, Integer bot,
- Integer left, Integer right,
+static void tui_grid_scroll(UI *ui, Integer g, Integer startrow, Integer endrow,
+ Integer startcol, Integer endcol,
Integer rows, Integer cols)
{
TUIData *data = ui->data;
UGrid *grid = &data->grid;
- ugrid_set_scroll_region(&data->grid, (int)top, (int)bot-1,
- (int)left, (int)right-1);
+ int top = (int)startrow, bot = (int)endrow-1;
+ int left = (int)startcol, right = (int)endcol-1;
- data->scroll_region_is_full_screen =
- left == 0 && right == ui->width
- && top == 0 && bot == ui->height;
+ bool fullwidth = left == 0 && right == ui->width-1;
+ data->scroll_region_is_full_screen = fullwidth
+ && top == 0 && bot == ui->height-1;
int clear_top, clear_bot;
- ugrid_scroll(grid, (int)rows, &clear_top, &clear_bot);
+ ugrid_scroll(grid, top, bot, left, right, (int)rows,
+ &clear_top, &clear_bot);
+
+ bool can_scroll = data->scroll_region_is_full_screen
+ || (data->can_change_scroll_region
+ && ((left == 0 && right == ui->width - 1)
+ || data->can_set_lr_margin
+ || data->can_set_left_right_margin));
- if (can_use_scroll(ui)) {
+ if (can_scroll) {
// Change terminal scroll region and move cursor to the top
if (!data->scroll_region_is_full_screen) {
- set_scroll_region(ui);
+ set_scroll_region(ui, top, bot, left, right);
}
- cursor_goto(ui, grid->top, grid->left);
+ cursor_goto(ui, top, left);
// also set default color attributes or some terminals can become funny
update_attrs(ui, data->clear_attrs);
@@ -1049,19 +1068,19 @@ static void tui_grid_scroll(UI *ui, Integer g, Integer top, Integer bot,
// Restore terminal scroll region and cursor
if (!data->scroll_region_is_full_screen) {
- reset_scroll_region(ui);
+ reset_scroll_region(ui, fullwidth);
}
cursor_goto(ui, data->row, data->col);
if (!(data->bce || no_bg(ui, data->clear_attrs))) {
// Scrolling will leave wrong background in the cleared area on non-BCE
// terminals. Update the cleared area.
- clear_region(ui, clear_top, clear_bot, grid->left, grid->right,
+ clear_region(ui, clear_top, clear_bot, left, right,
data->clear_attrs);
}
} else {
// Mark the entire scroll region as invalid for redrawing later
- invalidate(ui, grid->top, grid->bot, grid->left, grid->right);
+ invalidate(ui, top, bot, left, right);
}
}
@@ -1220,7 +1239,8 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
UGrid *grid = &data->grid;
for (Integer c = startcol; c < endcol; c++) {
memcpy(grid->cells[linerow][c].data, chunk[c-startcol], sizeof(schar_T));
- grid->cells[linerow][c].attrs = kv_A(data->attrs, attrs[c-startcol]);
+ assert((size_t)attrs[c-startcol] < kv_size(data->attrs));
+ grid->cells[linerow][c].attr = attrs[c-startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)linerow, (int)startcol,
(int)endcol-1, {
@@ -1231,7 +1251,7 @@ static void tui_raw_line(UI *ui, Integer g, Integer linerow, Integer startcol,
if (clearcol > endcol) {
HlAttrs cl_attrs = kv_A(data->attrs, (size_t)clearattr);
ugrid_clear_chunk(grid, (int)linerow, (int)endcol, (int)clearcol,
- cl_attrs);
+ (sattr_T)clearattr);
clear_region(ui, (int)linerow, (int)linerow, (int)endcol, (int)clearcol-1,
cl_attrs);
}
@@ -1419,6 +1439,18 @@ static int unibi_find_ext_str(unibi_term *ut, const char *name)
return -1;
}
+static int unibi_find_ext_bool(unibi_term *ut, const char *name)
+{
+ size_t max = unibi_count_ext_bool(ut);
+ for (size_t i = 0; i < max; i++) {
+ const char * n = unibi_get_ext_bool_name(ut, i);
+ if (n && 0 == strcmp(n, name)) {
+ return (int)i;
+ }
+ }
+ return -1;
+}
+
/// Patches the terminfo records after loading from system or built-in db.
/// Several entries in terminfo are known to be deficient or outright wrong;
/// and several terminal emulators falsely announce incorrect terminal types.
@@ -1446,6 +1478,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| terminfo_is_term_family(term, "iterm2")
|| terminfo_is_term_family(term, "iTerm.app")
|| terminfo_is_term_family(term, "iTerm2.app");
+ bool alacritty = terminfo_is_term_family(term, "alacritty");
// None of the following work over SSH; see :help TERM .
bool iterm_pretending_xterm = xterm && iterm_env;
bool konsole_pretending_xterm = xterm && konsole;
@@ -1640,6 +1673,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
// per analysis of VT100Terminal.m
|| iterm || iterm_pretending_xterm
|| teraterm // per TeraTerm "Supported Control Functions" doco
+ || alacritty // https://github.com/jwilm/alacritty/pull/608
// Some linux-type terminals implement the xterm extension.
// Example: console-terminal-emulator from the nosh toolset.
|| (linuxvt
@@ -1817,6 +1851,22 @@ static void augment_terminfo(TUIData *data, const char *term,
ut, "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h");
data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(
ut, "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l");
+
+ int ext_bool_Su = unibi_find_ext_bool(ut, "Su"); // used by kitty
+ if (vte_version >= 5102
+ || (ext_bool_Su != -1 && unibi_get_ext_bool(ut, (size_t)ext_bool_Su))) {
+ data->unibi_ext.enter_undercurl_mode = (int)unibi_add_ext_str(
+ ut, "ext.enter_undercurl_mode", "\x1b[4:3m");
+ data->unibi_ext.exit_undercurl_mode = (int)unibi_add_ext_str(
+ ut, "ext.exit_undercurl_mode", "\x1b[4:0m");
+ if (has_colon_rgb) {
+ data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(
+ ut, "ext.set_underline_color", "\x1b[58:2:%p1%d:%p2%d:%p3%dm");
+ } else {
+ data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(
+ ut, "ext.set_underline_color", "\x1b[58:2:%p1%d:%p2%d:%p3%dm");
+ }
+ }
}
static void flush_buf(UI *ui)
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
index 36936970f8..e2b92d7112 100644
--- a/src/nvim/ugrid.c
+++ b/src/nvim/ugrid.c
@@ -16,7 +16,6 @@
void ugrid_init(UGrid *grid)
{
- grid->attrs = HLATTRS_INIT;
grid->cells = NULL;
}
@@ -33,10 +32,6 @@ void ugrid_resize(UGrid *grid, int width, int height)
grid->cells[i] = xcalloc((size_t)width, sizeof(UCell));
}
- grid->top = 0;
- grid->bot = height - 1;
- grid->left = 0;
- grid->right = width - 1;
grid->row = grid->col = 0;
grid->width = width;
grid->height = height;
@@ -44,13 +39,12 @@ void ugrid_resize(UGrid *grid, int width, int height)
void ugrid_clear(UGrid *grid)
{
- clear_region(grid, 0, grid->height-1, 0, grid->width-1,
- HLATTRS_INIT);
+ clear_region(grid, 0, grid->height-1, 0, grid->width-1, 0);
}
-void ugrid_clear_chunk(UGrid *grid, int row, int col, int endcol, HlAttrs attrs)
+void ugrid_clear_chunk(UGrid *grid, int row, int col, int endcol, sattr_T attr)
{
- clear_region(grid, row, row, col, endcol-1, attrs);
+ clear_region(grid, row, row, col, endcol-1, attr);
}
void ugrid_goto(UGrid *grid, int row, int col)
@@ -59,25 +53,18 @@ void ugrid_goto(UGrid *grid, int row, int col)
grid->col = col;
}
-void ugrid_set_scroll_region(UGrid *grid, int top, int bot, int left, int right)
-{
- grid->top = top;
- grid->bot = bot;
- grid->left = left;
- grid->right = right;
-}
-
-void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
+void ugrid_scroll(UGrid *grid, int top, int bot, int left, int right,
+ int count, int *clear_top, int *clear_bot)
{
// Compute start/stop/step for the loop below
int start, stop, step;
if (count > 0) {
- start = grid->top;
- stop = grid->bot - count + 1;
+ start = top;
+ stop = bot - count + 1;
step = 1;
} else {
- start = grid->bot;
- stop = grid->top - count - 1;
+ start = bot;
+ stop = top - count - 1;
step = -1;
}
@@ -85,10 +72,10 @@ void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
// Copy cell data
for (i = start; i != stop; i += step) {
- UCell *target_row = grid->cells[i] + grid->left;
- UCell *source_row = grid->cells[i + count] + grid->left;
+ UCell *target_row = grid->cells[i] + left;
+ UCell *source_row = grid->cells[i + count] + left;
memcpy(target_row, source_row,
- sizeof(UCell) * (size_t)(grid->right - grid->left + 1));
+ sizeof(UCell) * (size_t)(right - left + 1));
}
// clear cells in the emptied region,
@@ -99,32 +86,16 @@ void ugrid_scroll(UGrid *grid, int count, int *clear_top, int *clear_bot)
*clear_bot = stop;
*clear_top = stop + count + 1;
}
- clear_region(grid, *clear_top, *clear_bot, grid->left, grid->right,
- HLATTRS_INIT);
-}
-
-UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size)
-{
- UCell *cell = grid->cells[grid->row] + grid->col;
- cell->data[size] = 0;
- cell->attrs = grid->attrs;
- assert(size <= CELLBYTES);
-
- if (text) {
- memcpy(cell->data, text, size);
- }
-
- grid->col += 1;
- return cell;
+ clear_region(grid, *clear_top, *clear_bot, left, right, 0);
}
static void clear_region(UGrid *grid, int top, int bot, int left, int right,
- HlAttrs attrs)
+ sattr_T attr)
{
UGRID_FOREACH_CELL(grid, top, bot, left, right, {
cell->data[0] = ' ';
cell->data[1] = 0;
- cell->attrs = attrs;
+ cell->attr = attr;
});
}
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
index 04e027bd46..af78fe91c5 100644
--- a/src/nvim/ugrid.h
+++ b/src/nvim/ugrid.h
@@ -11,14 +11,12 @@ typedef struct ugrid UGrid;
struct ucell {
char data[CELLBYTES + 1];
- HlAttrs attrs;
+ sattr_T attr;
};
struct ugrid {
- int top, bot, left, right;
int row, col;
int width, height;
- HlAttrs attrs;
UCell **cells;
};
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index df489f569f..d89ad60ce7 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -15,7 +15,7 @@ typedef enum {
kUITabline,
kUIWildmenu,
#define kUIGlobalCount (kUIWildmenu+1)
- kUINewgrid,
+ kUILinegrid,
kUIHlState,
kUIExtCount,
} UIExtension;
@@ -25,7 +25,7 @@ EXTERN const char *ui_ext_names[] INIT(= {
"ext_popupmenu",
"ext_tabline",
"ext_wildmenu",
- "ext_newgrid",
+ "ext_linegrid",
"ext_hlstate",
});
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 1e6be5d824..df0507ed41 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -891,7 +891,7 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi,
for (;; ) {
int len = undo_read_byte(bi);
- if (len == 0) {
+ if (len == 0 || len == EOF) {
break;
}
int what = undo_read_byte(bi);
diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index e0472977cc..c3b332c8e7 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -156,6 +156,6 @@ describe("ui_options in metadata", function()
local api = helpers.call('api_info')
local options = api.ui_options
eq({'rgb', 'ext_cmdline', 'ext_popupmenu',
- 'ext_tabline', 'ext_wildmenu', 'ext_newgrid', 'ext_hlstate'}, options)
+ 'ext_tabline', 'ext_wildmenu', 'ext_linegrid', 'ext_hlstate'}, options)
end)
end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 5261f57ca7..5ca133267d 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1254,7 +1254,7 @@ describe('API', function()
ext_popupmenu = false,
ext_tabline = false,
ext_wildmenu = false,
- ext_newgrid = screen._options.ext_newgrid or false,
+ ext_linegrid = screen._options.ext_linegrid or false,
ext_hlstate=false,
height = 4,
rgb = true,
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index 2a67453bce..15121261c7 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -4,6 +4,7 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
+local eval = helpers.eval
local feed = helpers.feed
local funcs = helpers.funcs
local nvim_prog = helpers.nvim_prog
@@ -196,5 +197,10 @@ describe('startup', function()
funcs.system({nvim_prog, '-n', '-es' },
{ 'set encoding', '' }))
end)
+
+ it('does not crash if --embed is given twice', function()
+ clear{args={'--embed'}}
+ eq(2, eval('1+1'))
+ end)
end)
diff --git a/test/functional/ex_cmds/recover_spec.lua b/test/functional/ex_cmds/recover_spec.lua
deleted file mode 100644
index cb68c29b9a..0000000000
--- a/test/functional/ex_cmds/recover_spec.lua
+++ /dev/null
@@ -1,76 +0,0 @@
-local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
-local feed_command, eq, clear, eval, feed, expect, source =
- helpers.feed_command, helpers.eq, helpers.clear, helpers.eval, helpers.feed,
- helpers.expect, helpers.source
-local command = helpers.command
-local ok = helpers.ok
-local rmdir = helpers.rmdir
-
-describe(':recover', function()
- before_each(clear)
-
- it('fails if given a non-existent swapfile', function()
- local swapname = 'bogus-swapfile'
- feed_command('recover '..swapname) -- This should not segfault. #2117
- eq('E305: No swap file found for '..swapname, eval('v:errmsg'))
- end)
-
-end)
-
-describe(':preserve', function()
- local swapdir = lfs.currentdir()..'/testdir_recover_spec'
- before_each(function()
- clear()
- rmdir(swapdir)
- lfs.mkdir(swapdir)
- end)
- after_each(function()
- command('%bwipeout!')
- rmdir(swapdir)
- end)
-
- it("saves to custom 'directory' and (R)ecovers (issue #1836)", function()
- local testfile = 'testfile_recover_spec'
- -- Put swapdir at the start of the 'directory' list. #1836
- -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
- -- attempt to create a swapfile in different directory.
- local init = [[
- set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
- set swapfile fileformat=unix undolevels=-1
- ]]
-
- source(init)
- command('edit! '..testfile)
- feed('isometext<esc>')
- command('preserve')
- source('redir => g:swapname | silent swapname | redir END')
-
- local swappath1 = eval('g:swapname')
-
- --TODO(justinmk): this is an ugly hack to force `helpers` to support
- --multiple sessions.
- local nvim2 = helpers.spawn({helpers.nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
- true)
- helpers.set_session(nvim2)
-
- source(init)
-
- -- Use the "SwapExists" event to choose the (R)ecover choice at the dialog.
- command('autocmd SwapExists * let v:swapchoice = "r"')
- command('silent edit! '..testfile)
- source('redir => g:swapname | silent swapname | redir END')
-
- local swappath2 = eval('g:swapname')
-
- expect('sometext')
- -- swapfile from session 1 should end in .swp
- eq(testfile..'.swp', string.match(swappath1, '[^%%]+$'))
- -- swapfile from session 2 should end in .swo
- eq(testfile..'.swo', string.match(swappath2, '[^%%]+$'))
- -- Verify that :swapname was not truncated (:help 'shortmess').
- ok(nil == string.find(swappath1, '%.%.%.'))
- ok(nil == string.find(swappath2, '%.%.%.'))
- end)
-
-end)
diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
new file mode 100644
index 0000000000..577a26178a
--- /dev/null
+++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
@@ -0,0 +1,154 @@
+local Screen = require('test.functional.ui.screen')
+local helpers = require('test.functional.helpers')(after_each)
+local lfs = require('lfs')
+local feed_command, eq, eval, expect, source =
+ helpers.feed_command, helpers.eq, helpers.eval, helpers.expect, helpers.source
+local clear = helpers.clear
+local command = helpers.command
+local feed = helpers.feed
+local nvim_prog = helpers.nvim_prog
+local ok = helpers.ok
+local rmdir = helpers.rmdir
+local set_session = helpers.set_session
+local spawn = helpers.spawn
+
+describe(':recover', function()
+ before_each(clear)
+
+ it('fails if given a non-existent swapfile', function()
+ local swapname = 'bogus-swapfile'
+ feed_command('recover '..swapname) -- This should not segfault. #2117
+ eq('E305: No swap file found for '..swapname, eval('v:errmsg'))
+ end)
+
+end)
+
+describe(':preserve', function()
+ local swapdir = lfs.currentdir()..'/Xtest_recover_dir'
+ before_each(function()
+ clear()
+ rmdir(swapdir)
+ lfs.mkdir(swapdir)
+ end)
+ after_each(function()
+ command('%bwipeout!')
+ rmdir(swapdir)
+ end)
+
+ it("saves to custom 'directory' and (R)ecovers #1836", function()
+ local testfile = 'Xtest_recover_file1'
+ -- Put swapdir at the start of the 'directory' list. #1836
+ -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
+ -- attempt to create a swapfile in different directory.
+ local init = [[
+ set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
+ set swapfile fileformat=unix undolevels=-1
+ ]]
+
+ source(init)
+ command('edit! '..testfile)
+ feed('isometext<esc>')
+ command('preserve')
+ source('redir => g:swapname | silent swapname | redir END')
+
+ local swappath1 = eval('g:swapname')
+
+ -- Start another Nvim instance.
+ local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
+ true)
+ set_session(nvim2)
+
+ source(init)
+
+ -- Use the "SwapExists" event to choose the (R)ecover choice at the dialog.
+ command('autocmd SwapExists * let v:swapchoice = "r"')
+ command('silent edit! '..testfile)
+ source('redir => g:swapname | silent swapname | redir END')
+
+ local swappath2 = eval('g:swapname')
+
+ expect('sometext')
+ -- swapfile from session 1 should end in .swp
+ eq(testfile..'.swp', string.match(swappath1, '[^%%]+$'))
+ -- swapfile from session 2 should end in .swo
+ eq(testfile..'.swo', string.match(swappath2, '[^%%]+$'))
+ -- Verify that :swapname was not truncated (:help 'shortmess').
+ ok(nil == string.find(swappath1, '%.%.%.'))
+ ok(nil == string.find(swappath2, '%.%.%.'))
+ end)
+
+end)
+
+describe('swapfile detection', function()
+ local swapdir = lfs.currentdir()..'/Xtest_swapdialog_dir'
+ before_each(function()
+ clear()
+ rmdir(swapdir)
+ lfs.mkdir(swapdir)
+ end)
+ after_each(function()
+ command('%bwipeout!')
+ rmdir(swapdir)
+ end)
+
+ it('always show swapfile dialog #8840 #9027', function()
+ local testfile = 'Xtest_swapdialog_file1'
+ -- Put swapdir at the start of the 'directory' list. #1836
+ -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
+ -- attempt to create a swapfile in different directory.
+ local init = [[
+ set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
+ set swapfile fileformat=unix undolevels=-1 hidden
+ ]]
+
+ local expected_no_dialog = '^'..(' '):rep(256)..'|\n'
+ for _=1,37 do
+ expected_no_dialog = expected_no_dialog..'~'..(' '):rep(255)..'|\n'
+ end
+ expected_no_dialog = expected_no_dialog..testfile..(' '):rep(216)..'0,0-1 All|\n'
+ expected_no_dialog = expected_no_dialog..(' '):rep(256)..'|\n'
+
+ source(init)
+ command('edit! '..testfile)
+ feed('isometext<esc>')
+ command('preserve')
+
+ -- Start another Nvim instance.
+ local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
+ true)
+ set_session(nvim2)
+ local screen2 = Screen.new(256, 40)
+ screen2:attach()
+ source(init)
+
+ -- With shortmess+=F
+ command('set shortmess+=F')
+ feed(':edit '..testfile..'<CR>')
+ screen2:expect{any=[[E325: ATTENTION.*]]..'\n'..[[Found a swap file by the name ".*]]
+ ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
+ feed('e') -- Chose "Edit" at the swap dialog.
+ screen2:expect(expected_no_dialog)
+
+ -- With :silent and shortmess+=F
+ feed(':silent edit %<CR>')
+ screen2:expect{any=[[Found a swap file by the name ".*]]
+ ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
+ feed('e') -- Chose "Edit" at the swap dialog.
+ screen2:expect(expected_no_dialog)
+
+ -- With :silent! and shortmess+=F
+ feed(':silent! edit %<CR>')
+ screen2:expect{any=[[Found a swap file by the name ".*]]
+ ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
+ feed('e') -- Chose "Edit" at the swap dialog.
+ screen2:expect(expected_no_dialog)
+
+ -- With API (via eval/VimL) call and shortmess+=F
+ feed(':call nvim_command("edit %")<CR>')
+ screen2:expect{any=[[Found a swap file by the name ".*]]
+ ..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
+ feed('e') -- Chose "Edit" at the swap dialog.
+ feed('<c-c>')
+ screen2:expect(expected_no_dialog)
+ end)
+end)
diff --git a/test/functional/options/shortmess_spec.lua b/test/functional/options/shortmess_spec.lua
index 96823476de..8ea9a19464 100644
--- a/test/functional/options/shortmess_spec.lua
+++ b/test/functional/options/shortmess_spec.lua
@@ -1,43 +1,96 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
local command = helpers.command
-local clear, feed_command = helpers.clear, helpers.feed_command
-
-if helpers.pending_win32(pending) then return end
+local eq = helpers.eq
+local eval = helpers.eval
+local feed = helpers.feed
describe("'shortmess'", function()
local screen
before_each(function()
clear()
- screen = Screen.new(25, 5)
+ screen = Screen.new(42, 5)
screen:attach()
end)
- after_each(function()
- screen:detach()
- end)
-
describe('"F" flag', function()
- it('hides messages about the files read', function()
- command("set shortmess-=F")
- feed_command('e test')
+ it('hides :edit fileinfo messages', function()
+ command('set hidden')
+ command('set shortmess-=F')
+ feed(':edit foo<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ "foo" [New File] |
+ ]])
+ eq(1, eval('bufnr("%")'))
+
+ command('set shortmess+=F')
+ feed(':edit bar<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ :edit bar |
+ ]])
+ eq(2, eval('bufnr("%")'))
+ end)
+
+ it('hides :bnext, :bprevious fileinfo messages', function()
+ command('set hidden')
+ command('set shortmess-=F')
+ feed(':edit foo<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ "foo" [New File] |
+ ]])
+ eq(1, eval('bufnr("%")'))
+ feed(':edit bar<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ "bar" [New File] |
+ ]])
+ eq(2, eval('bufnr("%")'))
+ feed(':bprevious<CR>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ "foo" [New file] --No lines in buffer-- |
+ ]])
+ eq(1, eval('bufnr("%")'))
+
+ command('set shortmess+=F')
+ feed(':bnext<CR>')
screen:expect([[
- ^ |
- ~ |
- ~ |
- ~ |
- "test" is a directory |
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ :bnext |
]])
- feed_command('set shortmess=F')
- feed_command('e test')
+ eq(2, eval('bufnr("%")'))
+ feed(':bprevious<CR>')
screen:expect([[
- ^ |
- ~ |
- ~ |
- ~ |
- :e test |
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ :bprevious |
]])
+ eq(1, eval('bufnr("%")'))
end)
end)
end)
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 351038e521..09f80ca849 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -258,10 +258,10 @@ describe('tui', function()
feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(v))})\013')
screen:expect([=[
[[['ext_cmdline', v:false], ['ext_hlstate', v:fals|
- e], ['ext_newgrid', v:true], ['ext_popupmenu', v:f|
- alse], ['ext_tabline', v:false], ['ext_wildmenu', |
- v:false], ['height', 6], ['rgb', v:false], ['width|
- ', 50]]] |
+ e], ['ext_linegrid', v:true], ['ext_popupmenu', v:|
+ false], ['ext_tabline', v:false], ['ext_wildmenu',|
+ v:false], ['height', 6], ['rgb', v:false], ['widt|
+ h', 50]]] |
{10:Press ENTER or type command to continue}{1: } |
{3:-- TERMINAL --} |
]=])
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index d39b24a00f..af26a6d88f 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -4,13 +4,13 @@ local clear, feed = helpers.clear, helpers.feed
local source = helpers.source
local command = helpers.command
-local function test_cmdline(newgrid)
+local function test_cmdline(linegrid)
local screen
before_each(function()
clear()
screen = Screen.new(25, 5)
- screen:attach({rgb=true, ext_cmdline=true, ext_newgrid=newgrid})
+ screen:attach({rgb=true, ext_cmdline=true, ext_linegrid=linegrid})
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {reverse = true},
@@ -608,7 +608,7 @@ local function test_cmdline(newgrid)
end)
end
--- the representation of cmdline and cmdline_block contents changed with ext_newgrid
+-- the representation of cmdline and cmdline_block contents changed with ext_linegrid
-- (which uses indexed highlights) so make sure to test both
describe('ui/ext_cmdline', function() test_cmdline(true) end)
describe('ui/ext_cmdline (legacy highlights)', function() test_cmdline(false) end)
diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua
index 6a5227803d..4fc93c3b63 100644
--- a/test/functional/ui/embed_spec.lua
+++ b/test/functional/ui/embed_spec.lua
@@ -5,14 +5,14 @@ local feed = helpers.feed
local eq = helpers.eq
local clear = helpers.clear
-local function test_embed(ext_newgrid)
+local function test_embed(ext_linegrid)
local screen
local function startup(...)
clear{headless=false, args={...}}
-- attach immediately after startup, for early UI
screen = Screen.new(60, 8)
- screen:attach{ext_newgrid=ext_newgrid}
+ screen:attach{ext_linegrid=ext_linegrid}
screen:set_default_attr_ids({
[1] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[2] = {bold = true, foreground = Screen.colors.SeaGreen4},
@@ -77,5 +77,5 @@ local function test_embed(ext_newgrid)
end)
end
-describe('--embed UI on startup (ext_newgrid=true)', function() test_embed(true) end)
-describe('--embed UI on startup (ext_newgrid=false)', function() test_embed(false) end)
+describe('--embed UI on startup (ext_linegrid=true)', function() test_embed(true) end)
+describe('--embed UI on startup (ext_linegrid=false)', function() test_embed(false) end)
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 4b6fbc0d74..7f3bf3e97b 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -40,24 +40,24 @@ describe('highlight: `:syntax manual`', function()
end)
it("works with buffer switch and 'hidden'", function()
- feed_command('e tmp1.vim')
- feed_command('e Xtest-functional-ui-highlight.tmp.vim')
- feed_command('filetype on')
- feed_command('syntax manual')
- feed_command('set ft=vim')
- feed_command('set syntax=ON')
+ command('e tmp1.vim')
+ command('e Xtest-functional-ui-highlight.tmp.vim')
+ command('filetype on')
+ command('syntax manual')
+ command('set ft=vim')
+ command('set syntax=ON')
feed('iecho 1<esc>0')
- feed_command('set hidden')
- feed_command('w')
- feed_command('bn')
+ command('set hidden')
+ command('w')
+ command('bn')
feed_command('bp')
screen:expect([[
{1:^echo} 1 |
{0:~ }|
{0:~ }|
{0:~ }|
- <f 1 --100%-- col 1 |
+ :bp |
]])
end)
@@ -919,7 +919,7 @@ describe("'winhighlight' highlight", function()
aa |
{0:~ }|
{4:[No Name] [+] }|
- <f 1 --100%-- col 1 |
+ |
]])
end)
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 322a94763f..99aae16183 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -30,15 +30,15 @@ describe('ui receives option updates', function()
ext_popupmenu=false,
ext_tabline=false,
ext_wildmenu=false,
- ext_newgrid=false,
+ ext_linegrid=false,
ext_hlstate=false,
}
it("for defaults", function()
screen:attach()
- -- NB: UI test suite can be run in both "newgrid" and legacy grid mode.
+ -- NB: UI test suite can be run in both "linegrid" and legacy grid mode.
-- In both cases check that the received value is the one requested.
- defaults.ext_newgrid = screen._options.ext_newgrid or false
+ defaults.ext_linegrid = screen._options.ext_linegrid or false
screen:expect(function()
eq(defaults, screen.options)
end)
@@ -46,7 +46,7 @@ describe('ui receives option updates', function()
it("when setting options", function()
screen:attach()
- defaults.ext_newgrid = screen._options.ext_newgrid or false
+ defaults.ext_linegrid = screen._options.ext_linegrid or false
local changed = {}
for k,v in pairs(defaults) do
changed[k] = v
@@ -95,7 +95,7 @@ describe('ui receives option updates', function()
end
screen:attach({ext_cmdline=true, ext_wildmenu=true})
- defaults.ext_newgrid = screen._options.ext_newgrid or false
+ defaults.ext_linegrid = screen._options.ext_linegrid or false
changed.ext_cmdline = true
changed.ext_wildmenu = true
screen:expect(function()
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 3831968f5b..691bf9f64c 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -185,11 +185,11 @@ function Screen:attach(options)
if options == nil then
options = {}
end
- if options.ext_newgrid == nil then
- options.ext_newgrid = true
+ if options.ext_linegrid == nil then
+ options.ext_linegrid = true
end
self._options = options
- self._clear_attrs = (options.ext_newgrid and {{},{}}) or {}
+ self._clear_attrs = (options.ext_linegrid and {{},{}}) or {}
uimeths.attach(self._width, self._height, options)
if self._options.rgb == nil then
-- nvim defaults to rgb=true internally,
@@ -386,9 +386,13 @@ function Screen:wait(check, timeout)
local err, checked = false
local success_seen = false
local failure_after_success = false
+ local did_flush = true
local function notification_cb(method, args)
assert(method == 'redraw')
- self:_redraw(args)
+ did_flush = self:_redraw(args)
+ if not did_flush then
+ return
+ end
err = check()
checked = true
if not err then
@@ -402,7 +406,9 @@ function Screen:wait(check, timeout)
return true
end
run(nil, notification_cb, nil, timeout or self.timeout)
- if not checked then
+ if not did_flush then
+ err = "no flush received"
+ elseif not checked then
err = check()
end
@@ -431,7 +437,8 @@ function Screen:sleep(ms)
end
function Screen:_redraw(updates)
- for _, update in ipairs(updates) do
+ local did_flush = false
+ for k, update in ipairs(updates) do
-- print('--')
-- print(require('inspect')(update))
local method = update[1]
@@ -446,7 +453,11 @@ function Screen:_redraw(updates)
self._on_event(method, update[i])
end
end
+ if k == #updates and method == "flush" then
+ did_flush = true
+ end
end
+ return did_flush
end
function Screen:set_on_event_handler(callback)
@@ -472,6 +483,10 @@ function Screen:_handle_resize(width, height)
}
end
+function Screen:_handle_flush()
+end
+
+
function Screen:_handle_grid_resize(grid, width, height)
assert(grid == 1)
self:_handle_resize(width, height)
@@ -609,6 +624,7 @@ function Screen:_handle_highlight_set(attrs)
end
function Screen:_handle_put(str)
+ assert(not self._options.ext_linegrid)
local cell = self._rows[self._cursor.row][self._cursor.col]
cell.text = str
cell.attrs = self._attrs
@@ -617,6 +633,7 @@ function Screen:_handle_put(str)
end
function Screen:_handle_grid_line(grid, row, col, items)
+ assert(self._options.ext_linegrid)
assert(grid == 1)
local line = self._rows[row+1]
local colpos = col+1
@@ -764,7 +781,7 @@ function Screen:_row_repr(row, attr_state)
local current_attr_id
for i = 1, self._width do
local attrs = row[i].attrs
- if self._options.ext_newgrid then
+ if self._options.ext_linegrid then
attrs = attrs[(self._options.rgb and 1) or 2]
end
local attr_id = self:_get_attr_id(attr_state, attrs, row[i].hl_id)
@@ -820,7 +837,7 @@ function Screen:_chunks_repr(chunks, attr_state)
for i, chunk in ipairs(chunks) do
local hl, text = unpack(chunk)
local attrs
- if self._options.ext_newgrid then
+ if self._options.ext_linegrid then
attrs = self._attr_table[hl][1]
else
attrs = hl
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index 957d8c0915..31825bdbf4 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -48,13 +48,13 @@ describe('screen', function()
end)
end)
-local function screen_tests(newgrid)
+local function screen_tests(linegrid)
local screen
before_each(function()
clear()
screen = Screen.new()
- screen:attach({rgb=true,ext_newgrid=newgrid})
+ screen:attach({rgb=true,ext_linegrid=linegrid})
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=255},
[1] = {bold=true, reverse=true},
diff --git a/test/helpers.lua b/test/helpers.lua
index 013fe60596..6ef7a456f4 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -1,6 +1,7 @@
local assert = require('luassert')
local luv = require('luv')
local lfs = require('lfs')
+local relpath = require('pl.path').relpath
local quote_me = '[^.%w%+%-%@%_%/]' -- complement (needn't quote)
local function shell_quote(str)
@@ -244,7 +245,7 @@ local function check_cores(app, force)
-- Workspace-local $TMPDIR, scrubbed and pattern-escaped.
-- "./Xtest-tmpdir/" => "Xtest%-tmpdir"
local local_tmpdir = (tmpdir_is_local(tmpdir_get())
- and tmpdir_get():gsub('^[ ./]+',''):gsub('%/+$',''):gsub('([^%w])', '%%%1')
+ and relpath(tmpdir_get()):gsub('^[ ./]+',''):gsub('%/+$',''):gsub('([^%w])', '%%%1')
or nil)
local db_cmd
if hasenv('NVIM_TEST_CORE_GLOB_DIRECTORY') then
@@ -645,8 +646,38 @@ local function hexdump(str)
return dump .. hex .. string.rep(" ", 8 - len % 8) .. asc
end
-local function read_file(name)
- local file = io.open(name, 'r')
+-- Reads text lines from `filename` into a table.
+--
+-- filename: path to file
+-- start: start line (1-indexed), negative means "lines before end" (tail)
+local function read_file_list(filename, start)
+ local lnum = (start ~= nil and type(start) == 'number') and start or 1
+ local tail = (lnum < 0)
+ local maxlines = tail and math.abs(lnum) or nil
+ local file = io.open(filename, 'r')
+ if not file then
+ return nil
+ end
+ local lines = {}
+ local i = 1
+ for line in file:lines() do
+ if i >= start then
+ table.insert(lines, line)
+ if #lines > maxlines then
+ table.remove(lines, 1)
+ end
+ end
+ i = i + 1
+ end
+ file:close()
+ return lines
+end
+
+-- Reads the entire contents of `filename` into a string.
+--
+-- filename: path to file
+local function read_file(filename)
+ local file = io.open(filename, 'r')
if not file then
return nil
end
@@ -684,18 +715,13 @@ end
-- Also removes the file, if the current environment looks like CI.
local function read_nvim_log()
local logfile = os.getenv('NVIM_LOG_FILE') or '.nvimlog'
- local logtext = read_file(logfile)
- local lines = {}
- for l in string.gmatch(logtext or '', "[^\n]+") do -- Split at newlines.
- table.insert(lines, l)
- end
+ local keep = isCI() and 999 or 10
+ local lines = read_file_list(logfile, -keep) or {}
local log = (('-'):rep(78)..'\n'
..string.format('$NVIM_LOG_FILE: %s\n', logfile)
- ..(logtext and (isCI() and '' or '(last 10 lines)\n') or '(empty)\n'))
- local keep = (isCI() and #lines or math.min(10, #lines))
- local startidx = math.max(1, #lines - keep + 1)
- for i = startidx, (startidx + keep - 1) do
- log = log..lines[i]..'\n'
+ ..(#lines > 0 and '(last '..tostring(keep)..' lines)\n' or '(empty)\n'))
+ for _,line in ipairs(lines) do
+ log = log..line..'\n'
end
log = log..('-'):rep(78)..'\n'
if isCI() then
@@ -733,6 +759,7 @@ local module = {
popen_r = popen_r,
popen_w = popen_w,
read_file = read_file,
+ read_file_list = read_file_list,
read_nvim_log = read_nvim_log,
repeated_read_cmd = repeated_read_cmd,
shallowcopy = shallowcopy,
diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt
index f770999c0f..4ca00b26cd 100644
--- a/third-party/CMakeLists.txt
+++ b/third-party/CMakeLists.txt
@@ -88,7 +88,7 @@ if(MINGW AND CMAKE_GENERATOR MATCHES "Ninja")
if(NOT MAKE_PRG)
message(FATAL_ERROR "GNU Make for mingw32 is required to build the dependecies.")
else()
- message(STATUS "Found GNU Make for mingw32 at ${MAKE_PRG}")
+ message(STATUS "Found GNU Make for mingw32: ${MAKE_PRG}")
endif()
endif()
@@ -141,8 +141,8 @@ set(LIBTERMKEY_SHA256 6c0d87c94ab9915e76ecd313baec08dedf3bd56de83743d9aa923a0819
set(LIBVTERM_URL https://github.com/neovim/libvterm/archive/3f62ac6b7bdffda39d68f723fb1806dfd6d6382d.tar.gz)
set(LIBVTERM_SHA256 1c8b318370f00f831f43e3ec86a48984250e3ee5c76beb106a421c9a42286ac5)
-set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/5.1.0/jemalloc-5.1.0.tar.bz2)
-set(JEMALLOC_SHA256 5396e61cc6103ac393136c309fae09e44d74743c86f90e266948c50f3dbb7268)
+set(JEMALLOC_URL https://github.com/jemalloc/jemalloc/releases/download/4.5.0/jemalloc-4.5.0.tar.bz2)
+set(JEMALLOC_SHA256 9409d85664b4f135b77518b0b118c549009dc10f6cba14557d170476611f6780)
set(LUV_URL https://github.com/luvit/luv/archive/1.9.1-1.tar.gz)
set(LUV_SHA256 562b9efaad30aa051a40eac9ade0c3df48bb8186763769abe47ec3fb3edb1268)