diff options
62 files changed, 938 insertions, 889 deletions
diff --git a/.travis.yml b/.travis.yml index 5c600ce823..74e4b3f76e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,7 @@ matrix: compiler: clang-3.9 env: > CLANG_SANITIZER=ASAN_UBSAN - CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUAJIT=false" + CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" - os: linux compiler: clang-3.9 env: CLANG_SANITIZER=TSAN diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d1c0e8fd5..ab7595eb11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,9 @@ set(NVIM_VERSION_PATCH 1) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 2) # Bump this after any API change. +set(NVIM_API_LEVEL 3) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) include(GetGitRevisionDescription) @@ -221,6 +221,11 @@ else() add_definitions(-Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99) + check_c_compiler_flag(-Wimplicit-fallthrough HAS_WIMPLICIT_FALLTHROUGH_FLAG) + if(HAS_WIMPLICIT_FALLTHROUGH_FLAG) + add_definitions(-Wimplicit-fallthrough) + endif() + # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang # 3.4.1 used there. if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") @@ -309,12 +314,18 @@ include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) find_package(Msgpack 1.0.0 REQUIRED) include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) -option(PREFER_LUAJIT "Prefer LuaJIT over Lua when compiling executable. Test library always uses luajit." ON) +# Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. +option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) -find_package(LuaJit REQUIRED) - -if(NOT PREFER_LUAJIT) +if(PREFER_LUA) find_package(Lua REQUIRED) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIR}) + set(LUA_PREFERRED_LIBRARIES ${LUA_LIBRARIES}) + find_package(LuaJit) +else() + find_package(LuaJit REQUIRED) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) + set(LUA_PREFERRED_LIBRARIES ${LUAJIT_LIBRARIES}) endif() list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim index 8f45adcff1..1d8cae7d19 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -66,6 +66,7 @@ function! health#check(plugin_names) abort " needed for plasticboy/vim-markdown, because it uses fdm=expr normal! zR setlocal nomodified + setlocal bufhidden=hide redraw|echo '' endfunction diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index c5826217c5..6183182b73 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -38,6 +38,16 @@ function! s:system_handler(jobid, data, event) dict abort endif endfunction +" Attempts to construct a shell command from an args list. +" Only for display, to help users debug a failed command. +function! s:shellify(cmd) abort + if type(a:cmd) != type([]) + return a:cmd + endif + return join(map(copy(a:cmd), + \'v:val =~# ''\m[\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ') +endfunction + " Run a system command and timeout after 30 seconds. function! s:system(cmd, ...) abort let stdin = a:0 ? a:1 : '' @@ -54,8 +64,7 @@ function! s:system(cmd, ...) abort let jobid = jobstart(a:cmd, opts) if jobid < 1 - call health#report_error(printf('Command error %d: %s', jobid, - \ type(a:cmd) == type([]) ? join(a:cmd) : a:cmd)) + call health#report_error(printf('Command error (job=%d): %s', jobid, s:shellify(a:cmd))) let s:shell_error = 1 return opts.output endif @@ -66,13 +75,11 @@ function! s:system(cmd, ...) abort let res = jobwait([jobid], 30000) if res[0] == -1 - call health#report_error(printf('Command timed out: %s', - \ type(a:cmd) == type([]) ? join(a:cmd) : a:cmd)) + call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd))) call jobstop(jobid) elseif s:shell_error != 0 && !ignore_error - call health#report_error(printf('Command error (%d) %s: %s', jobid, - \ type(a:cmd) == type([]) ? join(a:cmd) : a:cmd, - \ opts.output)) + call health#report_error(printf("Command error (job=%d): %s\nOutput: %s", jobid, + \ s:shellify(a:cmd), opts.output)) endif return opts.output @@ -157,7 +164,7 @@ function! s:version_info(python) abort \ ])) if empty(python_version) - let python_version = 'unable to parse python response' + let python_version = 'unable to parse '.a:python.' response' endif let nvim_path = s:trim(s:system([ @@ -176,7 +183,7 @@ function! s:version_info(python) abort endfunction " Try to get neovim.VERSION (added in 0.1.11dev). - let nvim_version = s:system(['python', '-c', + let nvim_version = s:system([a:python, '-c', \ 'from neovim import VERSION as v; '. \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'], \ '', 1, 1) diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 8db35c4590..ebc2a40561 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -48,6 +48,7 @@ version.api_compatible API is backwards-compatible with this level version.api_prerelease Declares the current API level as unstable > (version.api_prerelease && fn.since == version.api_level) functions API function signatures +ui_events UI event signatures |rpc-remote-ui| {fn}.since API level where function {fn} was introduced {fn}.deprecated_since API level where function {fn} was deprecated types Custom handle types defined by Nvim diff --git a/runtime/doc/if_lua.txt b/runtime/doc/if_lua.txt index 932564170d..470f3bde7a 100644 --- a/runtime/doc/if_lua.txt +++ b/runtime/doc/if_lua.txt @@ -1,21 +1,19 @@ -*if_lua.txt* For Neovim +*if_lua.txt* Nvim VIM REFERENCE MANUAL by Luis Carvalho -The Lua Interface to Vim *lua* *Lua* +Lua Interface to Nvim *lua* *Lua* -1. Commands |lua-commands| -2. The vim module |lua-vim| -3. The luaeval function |lua-luaeval| + Type <M-]> to see the table of contents. ============================================================================== 1. Commands *lua-commands* *:lua* :[range]lua {chunk} - Execute Lua chunk {chunk}. {not in Vi} + Execute Lua chunk {chunk}. Examples: > @@ -25,7 +23,7 @@ Examples: :[range]lua << {endmarker} {script} {endmarker} - Execute Lua script {script}. {not in Vi} + Execute Lua script {script}. Note: This command doesn't work when the Lua feature wasn't compiled in. To avoid errors, see |script-here|. @@ -48,8 +46,8 @@ Example: EOF endfunction -Note that in example variables are prefixed with local: they will disappear -when block finishes. This is not the case for globals. +Note that the variables are prefixed with `local`: they will disappear when +block finishes. This is not the case for globals. To see what version of Lua you have: > :lua print(_VERSION) @@ -66,7 +64,6 @@ If you use LuaJIT you can also use this: > If the value returned by the function is a string it becomes the text of the line in the current turn. The default for [range] is the whole file: "1,$". - {not in Vi} Examples: > @@ -80,7 +77,7 @@ Examples: *:luafile* :[range]luafile {file} - Execute Lua script in {file}. {not in Vi} + Execute Lua script in {file}. The whole argument is used as a single file name. Examples: @@ -107,7 +104,7 @@ position are restricted when the command is executed in the |sandbox|. Lua interfaces Vim through the "vim" module. Currently it only has `api` submodule which is a table with all API functions. Descriptions of these -functions may be found in |api-funcs.txt|. +functions may be found in |api.txt|. ============================================================================== 3. The luaeval function *lua-luaeval* *lua-eval* diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 9f448ae3ba..129a2dfed3 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -43,29 +43,35 @@ restarting the {cmd} when the session is loaded. ============================================================================== Input *terminal-emulator-input* -To send input, enter terminal mode using any command that would enter "insert +To send input, enter terminal-mode using any command that would enter "insert mode" in a normal buffer, such as |i| or |:startinsert|. In this mode all keys except <C-\><C-N> are sent to the underlying program. Use <C-\><C-N> to return -to normal mode. |CTRL-\_CTRL-N| +to normal-mode. |CTRL-\_CTRL-N| -Terminal mode has its own |:tnoremap| namespace for mappings, this can be used -to automate any terminal interaction. To map <Esc> to exit terminal mode: > +Terminal-mode has its own |:tnoremap| namespace for mappings, this can be used +to automate any terminal interaction. + +To map <Esc> to exit terminal-mode: > :tnoremap <Esc> <C-\><C-n> -< -Navigating to other windows is only possible in normal mode. For convenience, -you could use these mappings: > - :tnoremap <A-h> <C-\><C-n><C-w>h - :tnoremap <A-j> <C-\><C-n><C-w>j - :tnoremap <A-k> <C-\><C-n><C-w>k - :tnoremap <A-l> <C-\><C-n><C-w>l + +To simulate |i_CTRL-R| in terminal-mode: > + :tnoremap <expr> <C-R> '<C-\><C-N>"'.nr2char(getchar()).'pi' + +To use `ALT+{h,j,k,l}` to navigate windows from any mode: > + :tnoremap <A-h> <C-\><C-N><C-w>h + :tnoremap <A-j> <C-\><C-N><C-w>j + :tnoremap <A-k> <C-\><C-N><C-w>k + :tnoremap <A-l> <C-\><C-N><C-w>l + :inoremap <A-h> <C-\><C-N><C-w>h + :inoremap <A-j> <C-\><C-N><C-w>j + :inoremap <A-k> <C-\><C-N><C-w>k + :inoremap <A-l> <C-\><C-N><C-w>l :nnoremap <A-h> <C-w>h :nnoremap <A-j> <C-w>j :nnoremap <A-k> <C-w>k :nnoremap <A-l> <C-w>l -< -Then you can use `Alt+{h,j,k,l}` to navigate between windows from any mode. -Mouse input is supported, and has the following behavior: +Mouse input has the following behavior: - If the program has enabled mouse events, the corresponding events will be forwarded to the program. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index ca2ab836b8..7f456bf473 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -241,8 +241,8 @@ Lua interface (|if_lua.txt|): - `:lua error('TEST')` will print “TEST” as the error in Vim and “E5105: Error while calling lua chunk: [string "<VimL compiled string>"]:1: TEST” in Neovim. -- Lua has direct access to Neovim api via `vim.api`. -- Currently most of features are missing. +- Lua has direct access to Nvim |API| via `vim.api`. +- Currently, most legacy Vim features are missing. |input()| and |inputdialog()| gained support for each other’s features (return on cancel and completion respectively) via dictionary argument (replaces all @@ -250,16 +250,14 @@ other arguments if used). ============================================================================== 5. Missing legacy features *nvim-features-missing* - *if_lua* *if_perl* *if_mzscheme* *if_tcl* -These legacy Vim features may be implemented in the future, but they are not -planned for the current milestone. +Some legacy Vim features are not implemented: - |if_py|: vim.bindeval() and vim.Function() are not supported -- |if_lua| -- |if_perl| -- |if_mzscheme| -- |if_tcl| +- |if_lua|: the `vim` object currently only supports `vim.api` +- *if_perl* +- *if_mzscheme* +- *if_tcl* ============================================================================== 6. Removed features *nvim-features-removed* diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 8deeff4654..c46c0bed6d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -11,31 +11,38 @@ endif() endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) +set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) -set(MSGPACK_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genmsgpack.lua) +set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua) +set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua) set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack) set(MSGPACK_LUA_C_BINDINGS ${GENERATED_DIR}/msgpack_lua_c_bindings.generated.c) -set(HEADER_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gendeclarations.lua) +set(HEADER_GENERATOR ${GENERATOR_DIR}/gen_declarations.lua) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h) set(GENERATED_FUNCS_METADATA ${GENERATED_DIR}/api/private/funcs_metadata.generated.h) +set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h) +set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h) +set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h) +set(GENERATED_UI_EVENTS_BRIDGE ${GENERATED_DIR}/ui_events_bridge.generated.h) +set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata.generated.h) set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h) set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h) set(GENERATED_FUNCS ${GENERATED_DIR}/funcs.generated.h) set(GENERATED_EVENTS_ENUM ${GENERATED_INCLUDES_DIR}/auevents_enum.generated.h) set(GENERATED_EVENTS_NAMES_MAP ${GENERATED_DIR}/auevents_name_map.generated.h) set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h) -set(EX_CMDS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genex_cmds.lua) -set(FUNCS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/geneval.lua) -set(EVENTS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gen_events.lua) -set(OPTIONS_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genoptions.lua) -set(UNICODE_TABLES_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genunicodetables.lua) +set(EX_CMDS_GENERATOR ${GENERATOR_DIR}/gen_ex_cmds.lua) +set(FUNCS_GENERATOR ${GENERATOR_DIR}/gen_eval.lua) +set(EVENTS_GENERATOR ${GENERATOR_DIR}/gen_events.lua) +set(OPTIONS_GENERATOR ${GENERATOR_DIR}/gen_options.lua) +set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) set(VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) -set(CHAR_BLOB_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/gencharblob.lua) +set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") set(LINT_SUPPRESS_URL "${LINT_SUPPRESS_URL_BASE}/errors.json") @@ -49,6 +56,7 @@ set(LINT_SUPPRESSES_INSTALL_SCRIPT "${PROJECT_SOURCE_DIR}/cmake/InstallClintErro file(GLOB UNICODE_FILES ${UNICODE_DIR}/*.txt) file(GLOB API_HEADERS api/*.h) +list(REMOVE_ITEM API_HEADERS ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h) file(GLOB MSGPACK_RPC_HEADERS msgpack_rpc/*.h) include_directories(${GENERATED_DIR}) @@ -163,7 +171,7 @@ if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) endif() get_directory_property(gen_includes INCLUDE_DIRECTORIES) -foreach(gen_include ${gen_includes} ${LUAJIT_INCLUDE_DIRS}) +foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) list(APPEND gen_cflags "-I${gen_include}") endforeach() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) @@ -185,7 +193,11 @@ endfunction() # These lists must be mutually exclusive. foreach(sfile ${NVIM_SOURCES} "${CMAKE_CURRENT_LIST_DIR}/regexp_nfa.c" - ${GENERATED_API_DISPATCH}) + ${GENERATED_API_DISPATCH} + "${GENERATED_UI_EVENTS_CALL}" + "${GENERATED_UI_EVENTS_REMOTE}" + "${GENERATED_UI_EVENTS_BRIDGE}" + ) get_filename_component(full_d ${sfile} PATH) file(RELATIVE_PATH d "${CMAKE_CURRENT_LIST_DIR}" "${full_d}") if(${d} MATCHES "^[.][.]|auto/") @@ -228,7 +240,7 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES} add_custom_command( OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} ${API_METADATA} ${MSGPACK_LUA_C_BINDINGS} - COMMAND ${LUA_PRG} ${MSGPACK_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + COMMAND ${LUA_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA} ${API_METADATA} ${MSGPACK_LUA_C_BINDINGS} @@ -236,7 +248,7 @@ add_custom_command( DEPENDS ${API_HEADERS} ${MSGPACK_RPC_HEADERS} - ${MSGPACK_GENERATOR} + ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/api/dispatch_deprecated.lua ) @@ -253,6 +265,24 @@ list(APPEND NVIM_GENERATED_SOURCES "${MSGPACK_LUA_C_BINDINGS}" ) +add_custom_command( + OUTPUT ${GENERATED_UI_EVENTS} + ${GENERATED_UI_EVENTS_CALL} + ${GENERATED_UI_EVENTS_REMOTE} + ${GENERATED_UI_EVENTS_BRIDGE} + ${GENERATED_UI_EVENTS_METADATA} + COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h + ${GENERATED_UI_EVENTS} + ${GENERATED_UI_EVENTS_CALL} + ${GENERATED_UI_EVENTS_REMOTE} + ${GENERATED_UI_EVENTS_BRIDGE} + ${GENERATED_UI_EVENTS_METADATA} + DEPENDS + ${API_UI_EVENTS_GENERATOR} + ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h +) + list(APPEND NVIM_GENERATED_FOR_HEADERS "${GENERATED_EX_CMDS_ENUM}" "${GENERATED_EVENTS_ENUM}" @@ -337,24 +367,13 @@ if(UNIX) ) endif() -set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) -set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) +set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) if(CMAKE_VERSION VERSION_LESS "2.8.8") - if(PREFER_LUAJIT) - include_directories(${LUAJIT_INCLUDE_DIRS}) - else() - message(FATAL_ERROR - "Must support INCLUDE_DIRECTORIES target property to build") - endif() -endif() - -if(PREFER_LUAJIT) - list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) -else() - list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUA_LIBRARIES}) + # Use include_directories() because INCLUDE_DIRECTORIES target property + # is not supported + include_directories(${LUA_PREFERRED_INCLUDE_DIRS}) endif() -list(APPEND NVIM_TEST_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) # Don't use jemalloc in the unit test library. if(JEMALLOC_FOUND) @@ -366,11 +385,6 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) -if(PREFER_LUAJIT) - set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) -else() - set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIRS}) -endif() set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -428,8 +442,7 @@ add_library( ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ) set_property(TARGET libnvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) -target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) + INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) set_target_properties( libnvim PROPERTIES @@ -441,28 +454,32 @@ set_property( APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB " ) -add_library( - nvim-test - MODULE - EXCLUDE_FROM_ALL - ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} - ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${UNIT_TEST_FIXTURES} -) -target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) -set_property( - TARGET nvim-test - APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS} -) -set_target_properties( - nvim-test - PROPERTIES - POSITION_INDEPENDENT_CODE ON -) -set_property( - TARGET nvim-test - APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING " -) +if(LUAJIT_FOUND) + set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUAJIT_LIBRARIES}) + add_library( + nvim-test + MODULE + EXCLUDE_FROM_ALL + ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} + ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} + ${UNIT_TEST_FIXTURES} + ) + target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) + target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) + set_property( + TARGET nvim-test + APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS} + ) + set_target_properties( + nvim-test + PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) + set_property( + TARGET nvim-test + APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING " + ) +endif() if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") @@ -518,7 +535,7 @@ set(NO_SINGLE_CHECK_HEADERS foreach(hfile ${NVIM_HEADERS}) get_test_target(test-includes "${hfile}" relative_path texe) - if(NOT ${hfile} MATCHES "[.]c[.]h$") + if(NOT ${hfile} MATCHES "[.](c|in)[.]h$") set(tsource "${GENERATED_DIR}/${relative_path}.test-include.c") write_file("${tsource}" "#include \"${hfile}\"\nint main(int argc, char **argv) { return 0; }") add_executable( diff --git a/src/nvim/README.md b/src/nvim/README.md index f16c6de12f..3032913500 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,22 +1,27 @@ ## Source code overview -Since Neovim has inherited most code from Vim, some information in [its -README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) still -applies. +This document is an overview of how Nvim works internally, focusing on parts +that are different from Vim. Since Nvim inherited from Vim, some information in +[its README](https://raw.githubusercontent.com/vim/vim/master/src/README.txt) +still applies. -This document aims to give a high level overview of how Neovim works internally, -focusing on parts that are different from Vim. Currently this is still a work in -progress, especially because I have avoided adding too many details about parts -that are constantly changing. As the code becomes more organized and stable, -this document will be updated to reflect the changes. +For module-specific details, read the source code. Some files are extensively +commented at the top (e.g. terminal.c, screen.c). -If you are looking for module-specific details, it is best to read the source -code. Some files are extensively commented at the top (e.g. terminal.c, -screen.c). +### Source file name conventions + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. ### Top-level program loops -First let's understand what a Vim-like program does by analyzing the workflow of +Let's understand what a Vim-like program does by analyzing the workflow of a typical editing session: 01. Vim dispays the welcome screen @@ -41,16 +46,14 @@ a typical editing session: 21. User types: `word<ESC>` 22. Vim inserts "word" at the beginning and returns to normal mode -Note that we have split user actions into sequences of inputs that change the -state of the editor. While there's no documentation about a "g command -mode" (step 16), internally it is implemented similarly to "operator-pending -mode". +Note that we split user actions into sequences of inputs that change the state +of the editor. While there's no documentation about a "g command mode" (step +16), internally it is implemented similarly to "operator-pending mode". -From this we can see that Vim has the behavior of a input-driven state -machine (more specifically, a pushdown automaton since it requires a stack for +From this we can see that Vim has the behavior of an input-driven state machine +(more specifically, a pushdown automaton since it requires a stack for transitioning back from states). Assuming each state has a callback responsible -for handling keys, this pseudocode (a python-like language) shows a good -representation of the main program loop: +for handling keys, this pseudocode represents the main program loop: ```py def state_enter(state_callback, data): @@ -126,12 +129,11 @@ def insert_state(data, key): return true ``` -While the actual code is much more complicated, the above gives an idea of how -Neovim is organized internally. Some states like the `g_command_state` or -`get_operator_count_state` do not have a dedicated `state_enter` callback, but -are implicitly embedded into other states (this will change later as we continue -the refactoring effort). To start reading the actual code, here's the -recommended order: +The above gives an idea of how Nvim is organized internally. Some states like +the `g_command_state` or `get_operator_count_state` do not have a dedicated +`state_enter` callback, but are implicitly embedded into other states (this +will change later as we continue the refactoring effort). To start reading the +actual code, here's the recommended order: 1. `state_enter()` function (state.c). This is the actual program loop, note that a `VimState` structure is used, which contains function pointers @@ -154,14 +156,14 @@ modes managed by the `state_enter` loop: ### Async event support -One of the features Neovim added is the support for handling arbitrary +One of the features Nvim added is the support for handling arbitrary asynchronous events, which can include: -- msgpack-rpc requests +- RPC requests - job control callbacks -- timers (not implemented yet but the support code is already there) +- timers -Neovim implements this functionality by entering another event loop while +Nvim implements this functionality by entering another event loop while waiting for characters, so instead of: ```py @@ -171,7 +173,7 @@ def state_enter(state_callback, data): while state_callback(data, key) # invoke the callback for the current state ``` -Neovim program loop is more like: +Nvim program loop is more like: ```py def state_enter(state_callback, data): @@ -182,9 +184,9 @@ def state_enter(state_callback, data): where `event` is something the operating system delivers to us, including (but not limited to) user input. The `read_next_event()` part is internally -implemented by libuv, the platform layer used by Neovim. +implemented by libuv, the platform layer used by Nvim. -Since Neovim inherited its code from Vim, the states are not prepared to receive +Since Nvim inherited its code from Vim, the states are not prepared to receive "arbitrary events", so we use a special key to represent those (When a state receives an "arbitrary event", it normally doesn't do anything other update the screen). diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 4cc4b600d5..1d7b305da3 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -33,6 +33,7 @@ typedef struct { #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.c.generated.h" # include "api/private/funcs_metadata.generated.h" +# include "api/private/ui_events_metadata.generated.h" #endif /// Start block that may cause vimscript exceptions @@ -820,6 +821,7 @@ Dictionary api_metadata(void) if (!metadata.size) { PUT(metadata, "version", DICTIONARY_OBJ(version_dict())); init_function_metadata(&metadata); + init_ui_event_metadata(&metadata); init_error_type_metadata(&metadata); init_type_metadata(&metadata); } @@ -843,6 +845,22 @@ static void init_function_metadata(Dictionary *metadata) PUT(*metadata, "functions", functions); } +static void init_ui_event_metadata(Dictionary *metadata) +{ + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + if (msgpack_unpack_next(&unpacked, + (const char *)ui_events_metadata, + sizeof(ui_events_metadata), + NULL) != MSGPACK_UNPACK_SUCCESS) { + abort(); + } + Object ui_events; + msgpack_rpc_to_object(&unpacked.data, &ui_events); + msgpack_unpacked_destroy(&unpacked); + PUT(*metadata, "ui_events", ui_events); +} + static void init_error_type_metadata(Dictionary *metadata) { Dictionary types = ARRAY_DICT_INIT; @@ -885,6 +903,24 @@ static void init_type_metadata(Dictionary *metadata) PUT(*metadata, "types", DICTIONARY_OBJ(types)); } +String copy_string(String str) +{ + if (str.data != NULL) { + return (String){ .data = xmemdupz(str.data, str.size), .size = str.size }; + } else { + return (String)STRING_INIT; + } +} + +Array copy_array(Array array) +{ + Array rv = ARRAY_DICT_INIT; + for (size_t i = 0; i < array.size; i++) { + ADD(rv, copy_object(array.items[i])); + } + return rv; +} + /// Creates a deep clone of an object Object copy_object(Object obj) { @@ -896,15 +932,10 @@ Object copy_object(Object obj) return obj; case kObjectTypeString: - return STRING_OBJ(cstr_to_string(obj.data.string.data)); + return STRING_OBJ(copy_string(obj.data.string)); - case kObjectTypeArray: { - Array rv = ARRAY_DICT_INIT; - for (size_t i = 0; i < obj.data.array.size; i++) { - ADD(rv, copy_object(obj.data.array.items[i])); - } - return ARRAY_OBJ(rv); - } + case kObjectTypeArray: + return ARRAY_OBJ(copy_array(obj.data.array)); case kObjectTypeDictionary: { Dictionary rv = ARRAY_DICT_INIT; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 3c0e8bc049..1b29f2fc78 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -19,6 +19,7 @@ #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/ui.c.generated.h" +# include "ui_events_remote.generated.h" #endif typedef struct { @@ -52,7 +53,7 @@ void remote_ui_disconnect(uint64_t channel_id) void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictionary options, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(err, kErrorTypeException, "UI already attached for channel"); @@ -124,7 +125,7 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, } void nvim_ui_detach(uint64_t channel_id, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(err, kErrorTypeException, "UI is not attached for channel"); @@ -136,7 +137,7 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(err, kErrorTypeException, "UI is not attached for channel"); @@ -157,7 +158,7 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { api_set_error(error, kErrorTypeException, "UI is not attached for channel"); @@ -236,100 +237,6 @@ static void push_call(UI *ui, char *name, Array args) kv_A(data->buffer, kv_size(data->buffer) - 1).data.array = call; } -static void remote_ui_resize(UI *ui, int width, int height) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(width)); - ADD(args, INTEGER_OBJ(height)); - push_call(ui, "resize", args); -} - -static void remote_ui_clear(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "clear", args); -} - -static void remote_ui_eol_clear(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "eol_clear", args); -} - -static void remote_ui_cursor_goto(UI *ui, int row, int col) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(col)); - push_call(ui, "cursor_goto", args); -} - -static void remote_ui_update_menu(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "update_menu", args); -} - -static void remote_ui_busy_start(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_start", args); -} - -static void remote_ui_busy_stop(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "busy_stop", args); -} - -static void remote_ui_mouse_on(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "mouse_on", args); -} - -static void remote_ui_mouse_off(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "mouse_off", args); -} - -static void remote_ui_mode_change(UI *ui, int mode_idx) -{ - Array args = ARRAY_DICT_INIT; - - char *full_name = shape_table[mode_idx].full_name; - ADD(args, STRING_OBJ(cstr_to_string(full_name))); - - ADD(args, INTEGER_OBJ(mode_idx)); - push_call(ui, "mode_change", args); -} - -static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left, - int right) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(top)); - ADD(args, INTEGER_OBJ(bot)); - ADD(args, INTEGER_OBJ(left)); - ADD(args, INTEGER_OBJ(right)); - push_call(ui, "set_scroll_region", args); -} - -static void remote_ui_scroll(UI *ui, int count) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(count)); - push_call(ui, "scroll", args); -} - -static void remote_ui_mode_info_set(UI *ui, bool guicursor_enabled, Array data) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, BOOLEAN_OBJ(guicursor_enabled)); - ADD(args, copy_object(ARRAY_OBJ(data))); - push_call(ui, "mode_info_set", args); -} static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) { @@ -372,47 +279,6 @@ static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) push_call(ui, "highlight_set", args); } -static void remote_ui_put(UI *ui, uint8_t *data, size_t size) -{ - Array args = ARRAY_DICT_INIT; - String str = { .data = xmemdupz(data, size), .size = size }; - ADD(args, STRING_OBJ(str)); - push_call(ui, "put", args); -} - -static void remote_ui_bell(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "bell", args); -} - -static void remote_ui_visual_bell(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "visual_bell", args); -} - -static void remote_ui_update_fg(UI *ui, int fg) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(fg)); - push_call(ui, "update_fg", args); -} - -static void remote_ui_update_bg(UI *ui, int bg) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(bg)); - push_call(ui, "update_bg", args); -} - -static void remote_ui_update_sp(UI *ui, int sp) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, INTEGER_OBJ(sp)); - push_call(ui, "update_sp", args); -} - static void remote_ui_flush(UI *ui) { UIData *data = ui->data; @@ -422,26 +288,6 @@ static void remote_ui_flush(UI *ui) } } -static void remote_ui_suspend(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "suspend", args); -} - -static void remote_ui_set_title(UI *ui, char *title) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(title))); - push_call(ui, "set_title", args); -} - -static void remote_ui_set_icon(UI *ui, char *icon) -{ - Array args = ARRAY_DICT_INIT; - ADD(args, STRING_OBJ(cstr_to_string(icon))); - push_call(ui, "set_icon", args); -} - static void remote_ui_event(UI *ui, char *name, Array args, bool *args_consumed) { Array my_args = ARRAY_DICT_INIT; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h new file mode 100644 index 0000000000..45d04335e4 --- /dev/null +++ b/src/nvim/api/ui_events.in.h @@ -0,0 +1,71 @@ +#ifndef NVIM_API_UI_EVENTS_IN_H +#define NVIM_API_UI_EVENTS_IN_H + +// This file is not compiled, just parsed for definitons +#ifdef INCLUDE_GENERATED_DECLARATIONS +# error "don't include this file, include nvim/ui.h" +#endif + +#include "nvim/api/private/defs.h" +#include "nvim/func_attr.h" +#include "nvim/ui.h" + +void resize(Integer rows, Integer columns) + FUNC_API_SINCE(3); +void clear(void) + FUNC_API_SINCE(3); +void eol_clear(void) + FUNC_API_SINCE(3); +void cursor_goto(Integer row, Integer col) + FUNC_API_SINCE(3); +void mode_info_set(Boolean enabled, Array cursor_styles) + FUNC_API_SINCE(3); +void update_menu(void) + FUNC_API_SINCE(3); +void busy_start(void) + FUNC_API_SINCE(3); +void busy_stop(void) + FUNC_API_SINCE(3); +void mouse_on(void) + FUNC_API_SINCE(3); +void mouse_off(void) + FUNC_API_SINCE(3); +void mode_change(String mode, Integer mode_idx) + FUNC_API_SINCE(3); +void set_scroll_region(Integer top, Integer bot, Integer left, Integer right) + FUNC_API_SINCE(3); +void scroll(Integer count) + FUNC_API_SINCE(3); +void highlight_set(HlAttrs attrs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void put(String str) + FUNC_API_SINCE(3); +void bell(void) + FUNC_API_SINCE(3); +void visual_bell(void) + FUNC_API_SINCE(3); +void flush(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; +void update_fg(Integer fg) + FUNC_API_SINCE(3); +void update_bg(Integer bg) + FUNC_API_SINCE(3); +void update_sp(Integer sp) + FUNC_API_SINCE(3); +void suspend(void) + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void set_title(String title) + FUNC_API_SINCE(3); +void set_icon(String icon) + FUNC_API_SINCE(3); + +void popupmenu_show(Array items, Integer selected, Integer row, Integer col) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void popupmenu_hide(void) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void popupmenu_select(Integer selected) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; +void tabline_update(Tabpage current, Array tabs) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; + +#endif // NVIM_API_UI_EVENTS_IN_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 12e8194c15..1fedaf30ef 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/buffer.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/file_search.h" @@ -254,6 +255,25 @@ free_vim_args: return rv; } +/// Execute lua code. Parameters might be passed, they are available inside +/// the chunk as `...`. The chunk can return a value. +/// +/// To evaluate an expression, it must be prefixed with "return ". For +/// instance, to call a lua function with arguments sent in and get its +/// return value back, use the code "return my_function(...)". +/// +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + /// Calculates the number of display cells occupied by `text`. /// <Tab> counts as one cell. /// @@ -659,7 +679,7 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err) /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_subscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : METHOD_MAXLEN); char e[METHOD_MAXLEN + 1]; @@ -673,7 +693,7 @@ void nvim_subscribe(uint64_t channel_id, String event) /// @param channel_id Channel id (passed automatically by the dispatcher) /// @param event Event type string void nvim_unsubscribe(uint64_t channel_id, String event) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { size_t length = (event.size < METHOD_MAXLEN ? event.size : @@ -722,7 +742,7 @@ Dictionary nvim_get_mode(void) } Array nvim_get_api_info(uint64_t channel_id) - FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; @@ -755,7 +775,7 @@ Array nvim_get_api_info(uint64_t channel_id) /// which resulted in an error, the error type and the error message. If an /// error ocurred, the values from all preceding calls will still be returned. Array nvim_call_atomic(uint64_t channel_id, Array calls, Error *err) - FUNC_API_SINCE(1) FUNC_API_NOEVAL + FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; Array results = ARRAY_DICT_INIT; diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 4ed75d8a2d..e3897e3929 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -24,6 +24,7 @@ #include <inttypes.h> #include "nvim/api/private/handle.h" +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/vim.h" @@ -3001,13 +3002,13 @@ static bool ti_change(char_u *str, char_u **last) return false; } -/* - * Put current window title back (used after calling a shell) - */ + +/// Set current window title void resettitle(void) { - ui_set_title((char *)lasttitle); - ui_set_icon((char *)lasticon); + ui_call_set_title(cstr_as_string((char *)lasttitle)); + ui_call_set_icon(cstr_as_string((char *)lasticon)); + ui_flush(); } # if defined(EXITFREE) @@ -3606,6 +3607,7 @@ int build_stl_str_hl( case STL_OFFSET_X: base = kNumBaseHexadecimal; + // fallthrough case STL_OFFSET: { long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); @@ -3616,6 +3618,7 @@ int build_stl_str_hl( } case STL_BYTEVAL_X: base = kNumBaseHexadecimal; + // fallthrough case STL_BYTEVAL: num = byteval; if (num == NL) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 678fa851eb..bfdec90a32 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4916,14 +4916,17 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) if (ctrl_x_mode == CTRL_X_DICTIONARY || ctrl_x_mode == CTRL_X_THESAURUS) break; + // fallthrough case '~': if (!p_magic) /* quote these only if magic is set */ break; + // fallthrough case '\\': if (ctrl_x_mode == CTRL_X_DICTIONARY || ctrl_x_mode == CTRL_X_THESAURUS) break; - case '^': /* currently it's not needed. */ + // fallthrough + case '^': // currently it's not needed. case '$': m++; if (dest != NULL) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 882b824b49..75cf564f55 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9596,13 +9596,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (from) { break; } - case kCdScopeTab: // FALLTHROUGH + // fallthrough + case kCdScopeTab: assert(tp); from = tp->tp_localdir; if (from) { break; } - case kCdScopeGlobal: // FALLTHROUGH + // fallthrough + case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. from = globaldir; } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index dd3fe22694..f017f57b12 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1896,7 +1896,7 @@ void tv_free(typval_T *tv) } case VAR_FUNC: { func_unref(tv->vval.v_string); - // FALLTHROUGH + FALLTHROUGH; } case VAR_STRING: { xfree(tv->vval.v_string); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 018a228843..a528a65abb 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4878,8 +4878,9 @@ void fix_help_buffer(void) continue; e1 = vim_strrchr(t1, '.'); e2 = vim_strrchr(path_tail(f2), '.'); - if (e1 == NUL || e2 == NUL) + if (e1 == NULL || e2 == NULL) { continue; + } if (fnamecmp(e1, ".txt") != 0 && fnamecmp(e1, fname + 4) != 0) { /* Not .txt and not .abx, remove it. */ @@ -5949,9 +5950,8 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // :sign define {name} {args}... {last}= // | | // last p - if (p == NUL) - { - /* Expand last argument name (before equal sign). */ + if (p == NULL) { + // Expand last argument name (before equal sign). xp->xp_pattern = last; switch (cmd_idx) { diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index cb02befeaf..a0406cf418 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6285,13 +6285,14 @@ static void ex_stop(exarg_T *eap) autowrite_all(); } ui_cursor_goto((int)Rows - 1, 0); - ui_putc('\n'); + ui_linefeed(); + ui_flush(); + ui_call_suspend(); // call machine specific function ui_flush(); - ui_suspend(); /* call machine specific function */ maketitle(); - resettitle(); /* force updating the title */ + resettitle(); // force updating the title redraw_later_clear(); - ui_refresh(); /* may have resized window */ + ui_refresh(); // may have resized window } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index fe45ba4568..0c14bf4255 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1268,6 +1268,7 @@ static int command_line_handle_key(CommandLineState *s) } return command_line_changed(s); } + // fallthrough case K_UP: case K_DOWN: @@ -5401,7 +5402,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp) if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) { *lenp = STRLEN(eap->arg); - return xmemdupz(eap->arg, *lenp); + return eap->skip ? NULL : xmemdupz(eap->arg, *lenp); } garray_T ga = { .ga_data = NULL, .ga_len = 0 }; diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 1049b5b7a6..cc94a41f80 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -89,6 +89,10 @@ # undef FUNC_ATTR_NONNULL_RET #endif +#ifdef FUNC_ATTR_NORETURN +# undef FUNC_ATTR_NORETURN +#endif + #ifndef DID_REAL_ATTR # define DID_REAL_ATTR # ifdef __GNUC__ @@ -107,6 +111,7 @@ # define REAL_FATTR_UNUSED __attribute__((unused)) # define REAL_FATTR_NONNULL_ALL __attribute__((nonnull)) # define REAL_FATTR_NONNULL_ARG(...) __attribute__((nonnull(__VA_ARGS__))) +# define REAL_FATTR_NORETURN __attribute__((noreturn)) # ifdef __clang__ // clang only @@ -176,12 +181,16 @@ # ifndef REAL_FATTR_NONNULL_RET # define REAL_FATTR_NONNULL_RET # endif + +# ifndef REAL_FATTR_NORETURN +# define REAL_FATTR_NORETURN +# endif #endif #ifdef DEFINE_FUNC_ATTRIBUTES # define FUNC_API_ASYNC # define FUNC_API_NOEXPORT -# define FUNC_API_NOEVAL +# define FUNC_API_REMOTE_ONLY # define FUNC_API_SINCE(X) # define FUNC_API_DEPRECATED_SINCE(X) # define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC @@ -196,6 +205,7 @@ # define FUNC_ATTR_NONNULL_ALL REAL_FATTR_NONNULL_ALL # define FUNC_ATTR_NONNULL_ARG(...) REAL_FATTR_NONNULL_ARG(__VA_ARGS__) # define FUNC_ATTR_NONNULL_RET REAL_FATTR_NONNULL_RET +# define FUNC_ATTR_NORETURN REAL_FATTR_NORETURN #elif !defined(DO_NOT_DEFINE_EMPTY_ATTRIBUTES) # define FUNC_ATTR_MALLOC # define FUNC_ATTR_ALLOC_SIZE(x) @@ -209,4 +219,5 @@ # define FUNC_ATTR_NONNULL_ALL # define FUNC_ATTR_NONNULL_ARG(...) # define FUNC_ATTR_NONNULL_RET +# define FUNC_ATTR_NORETURN #endif diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua new file mode 100644 index 0000000000..d3047e1a9c --- /dev/null +++ b/src/nvim/generators/c_grammar.lua @@ -0,0 +1,50 @@ +lpeg = require('lpeg') + +-- lpeg grammar for building api metadata from a set of header files. It +-- ignores comments and preprocessor commands and parses a very small subset +-- of C prototypes with a limited set of types +local P, R, S = lpeg.P, lpeg.R, lpeg.S +local C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg + +local any = P(1) -- (consume one character) +local letter = R('az', 'AZ') + S('_$') +local num = R('09') +local alpha = letter + num +local nl = P('\r\n') + P('\n') +local not_nl = any - nl +local ws = S(' \t') + nl +local fill = ws ^ 0 +local c_comment = P('//') * (not_nl ^ 0) +local c_preproc = P('#') * (not_nl ^ 0) +local typed_container = + (P('ArrayOf(') + P('DictionaryOf(')) * ((any - P(')')) ^ 1) * P(')') +local c_id = ( + typed_container + + (letter * (alpha ^ 0)) +) +local c_void = P('void') +local c_param_type = ( + ((P('Error') * fill * P('*') * fill) * Cc('error')) + + (C(c_id) * (ws ^ 1)) + ) +local c_type = (C(c_void) * (ws ^ 1)) + c_param_type +local c_param = Ct(c_param_type * C(c_id)) +local c_param_list = c_param * (fill * (P(',') * fill * c_param) ^ 0) +local c_params = Ct(c_void + c_param_list) +local c_proto = Ct( + Cg(c_type, 'return_type') * Cg(c_id, 'name') * + fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') * + Cg(Cc(false), 'async') * + (fill * Cg((P('FUNC_API_SINCE(') * C(num ^ 1)) * P(')'), 'since') ^ -1) * + (fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(num ^ 1)) * P(')'), + 'deprecated_since') ^ -1) * + (fill * Cg((P('FUNC_API_ASYNC') * Cc(true)), 'async') ^ -1) * + (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) * + (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) * + (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * + (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * + fill * P(';') + ) + +local grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1) +return {grammar=grammar, typed_container=typed_container} diff --git a/src/nvim/generators/dump_bin_array.lua b/src/nvim/generators/dump_bin_array.lua new file mode 100644 index 0000000000..bee5aba73f --- /dev/null +++ b/src/nvim/generators/dump_bin_array.lua @@ -0,0 +1,17 @@ +local function dump_bin_array(output, name, data) + output:write([[ + static const uint8_t ]]..name..[[[] = { +]]) + + for i = 1, #data do + output:write(string.byte(data, i)..', ') + if i % 10 == 0 then + output:write('\n ') + end + end + output:write([[ +}; +]]) +end + +return dump_bin_array diff --git a/scripts/genmsgpack.lua b/src/nvim/generators/gen_api_dispatch.lua index 86a051fb4c..b01321e713 100644 --- a/scripts/genmsgpack.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -1,51 +1,5 @@ -lpeg = require('lpeg') mpack = require('mpack') --- lpeg grammar for building api metadata from a set of header files. It --- ignores comments and preprocessor commands and parses a very small subset --- of C prototypes with a limited set of types -P, R, S = lpeg.P, lpeg.R, lpeg.S -C, Ct, Cc, Cg = lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cg - -any = P(1) -- (consume one character) -letter = R('az', 'AZ') + S('_$') -num = R('09') -alpha = letter + num -nl = P('\r\n') + P('\n') -not_nl = any - nl -ws = S(' \t') + nl -fill = ws ^ 0 -c_comment = P('//') * (not_nl ^ 0) -c_preproc = P('#') * (not_nl ^ 0) -typed_container = - (P('ArrayOf(') + P('DictionaryOf(')) * ((any - P(')')) ^ 1) * P(')') -c_id = ( - typed_container + - (letter * (alpha ^ 0)) -) -c_void = P('void') -c_param_type = ( - ((P('Error') * fill * P('*') * fill) * Cc('error')) + - (C(c_id) * (ws ^ 1)) - ) -c_type = (C(c_void) * (ws ^ 1)) + c_param_type -c_param = Ct(c_param_type * C(c_id)) -c_param_list = c_param * (fill * (P(',') * fill * c_param) ^ 0) -c_params = Ct(c_void + c_param_list) -c_proto = Ct( - Cg(c_type, 'return_type') * Cg(c_id, 'name') * - fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') * - Cg(Cc(false), 'async') * - (fill * Cg((P('FUNC_API_SINCE(') * C(num ^ 1)) * P(')'), 'since') ^ -1) * - (fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(num ^ 1)) * P(')'), - 'deprecated_since') ^ -1) * - (fill * Cg((P('FUNC_API_ASYNC') * Cc(true)), 'async') ^ -1) * - (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) * - (fill * Cg((P('FUNC_API_NOEVAL') * Cc(true)), 'noeval') ^ -1) * - fill * P(';') - ) -grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1) - -- we need at least 4 arguments since the last two are output files if arg[1] == '--help' then print('Usage: genmsgpack.lua args') @@ -59,8 +13,8 @@ end assert(#arg >= 4) functions = {} -local nvimsrcdir = arg[1] -package.path = nvimsrcdir .. '/?.lua;' .. package.path +local nvimdir = arg[1] +package.path = nvimdir .. '/?.lua;' .. package.path -- names of all headers relative to the source root (for inclusion in the -- generated file) @@ -77,6 +31,8 @@ lua_c_bindings_outputf = arg[5] -- set of function names, used to detect duplicates function_names = {} +c_grammar = require('generators.c_grammar') + -- read each input file, parse and append to the api metadata for i = 6, #arg do local full_path = arg[i] @@ -87,7 +43,8 @@ for i = 6, #arg do headers[#headers + 1] = parts[#parts - 1]..'/'..parts[#parts] local input = io.open(full_path, 'rb') - local tmp = grammar:match(input:read('*all')) + + local tmp = c_grammar.grammar:match(input:read('*all')) for i = 1, #tmp do local fn = tmp[i] if not fn.noexport then @@ -148,7 +105,7 @@ for i,f in ipairs(shallowcopy(functions)) do ismethod = true end else - f.noeval = true + f.remote_only = true f.since = 0 f.deprecated_since = 1 end @@ -170,7 +127,7 @@ for i,f in ipairs(shallowcopy(functions)) do newf.return_type = "Object" end newf.impl_name = f.name - newf.noeval = true + newf.remote_only = true newf.since = 0 newf.deprecated_since = 1 functions[#functions+1] = newf @@ -192,23 +149,12 @@ for _,f in ipairs(functions) do end -funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') -funcs_metadata_output:write([[ -static const uint8_t funcs_metadata[] = { -]]) - -- serialize the API metadata using msgpack and embed into the resulting -- binary for easy querying by clients -packed_exported_functions = mpack.pack(exported_functions) -for i = 1, #packed_exported_functions do - funcs_metadata_output:write(string.byte(packed_exported_functions, i)..', ') - if i % 10 == 0 then - funcs_metadata_output:write('\n ') - end -end -funcs_metadata_output:write([[ -}; -]]) +funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') +packed = mpack.pack(exported_functions) +dump_bin_array = require("generators.dump_bin_array") +dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed) funcs_metadata_output:close() -- start building the dispatch wrapper output @@ -216,7 +162,7 @@ output = io.open(dispatch_outputf, 'wb') local function real_type(type) local rv = type - if typed_container:match(rv) then + if c_grammar.typed_container:match(rv) then if rv:match('Array') then rv = 'Array' else @@ -486,7 +432,7 @@ local function process_function(fn) end for _, fn in ipairs(functions) do - if not fn.noeval or fn.name:sub(1, 4) == '_vim' then + if not fn.remote_only or fn.name:sub(1, 4) == '_vim' then process_function(fn) end end diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua new file mode 100644 index 0000000000..acdb25ca67 --- /dev/null +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -0,0 +1,165 @@ +mpack = require('mpack') + +local nvimdir = arg[1] +package.path = nvimdir .. '/?.lua;' .. package.path + +assert(#arg == 7) +input = io.open(arg[2], 'rb') +proto_output = io.open(arg[3], 'wb') +call_output = io.open(arg[4], 'wb') +remote_output = io.open(arg[5], 'wb') +bridge_output = io.open(arg[6], 'wb') +metadata_output = io.open(arg[7], 'wb') + +c_grammar = require('generators.c_grammar') +local events = c_grammar.grammar:match(input:read('*all')) + +function write_signature(output, ev, prefix, notype) + output:write('('..prefix) + if prefix == "" and #ev.parameters == 0 then + output:write('void') + end + for j = 1, #ev.parameters do + if j > 1 or prefix ~= '' then + output:write(', ') + end + local param = ev.parameters[j] + if not notype then + output:write(param[1]..' ') + end + output:write(param[2]) + end + output:write(')') +end + +function write_arglist(output, ev, need_copy) + output:write(' Array args = ARRAY_DICT_INIT;\n') + for j = 1, #ev.parameters do + local param = ev.parameters[j] + local kind = string.upper(param[1]) + local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING") + output:write(' ADD(args, ') + if do_copy then + output:write('copy_object(') + end + output:write(kind..'_OBJ('..param[2]..')') + if do_copy then + output:write(')') + end + output:write(');\n') + end +end + +for i = 1, #events do + ev = events[i] + assert(ev.return_type == 'void') + + if ev.since == nil then + print("Ui event "..ev.name.." lacks since field.\n") + os.exit(1) + end + ev.since = tonumber(ev.since) + + if not ev.remote_only then + proto_output:write(' void (*'..ev.name..')') + write_signature(proto_output, ev, 'UI *ui') + proto_output:write(';\n') + + if not ev.remote_impl then + remote_output:write('static void remote_ui_'..ev.name) + write_signature(remote_output, ev, 'UI *ui') + remote_output:write('\n{\n') + write_arglist(remote_output, ev, true) + remote_output:write(' push_call(ui, "'..ev.name..'", args);\n') + remote_output:write('}\n\n') + end + + if not ev.bridge_impl then + + send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' + argc = 1 + for j = 1, #ev.parameters do + local param = ev.parameters[j] + copy = 'copy_'..param[2] + if param[1] == 'String' then + send = send..' String copy_'..param[2]..' = copy_string('..param[2]..');\n' + argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)' + recv = (recv..' String '..param[2].. + ' = (String){.data = argv['..argc..'],'.. + '.size = (size_t)argv['..(argc+1)..']};\n') + recv_argv = recv_argv..', '..param[2] + recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' + argc = argc+2 + elseif param[1] == 'Array' then + send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n' + argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' + recv = (recv..' Array '..param[2].. + ' = (Array){.items = argv['..argc..'],'.. + '.size = (size_t)argv['..(argc+1)..']};\n') + recv_argv = recv_argv..', '..param[2] + recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' + argc = argc+2 + elseif param[1] == 'Integer' or param[1] == 'Boolean' then + argv = argv..', INT2PTR('..param[2]..')' + recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' + argc = argc+1 + else + assert(false) + end + end + bridge_output:write('static void ui_bridge_'..ev.name.. + '_event(void **argv)\n{\n') + bridge_output:write(' UI *ui = UI(argv[0]);\n') + bridge_output:write(recv) + bridge_output:write(' ui->'..ev.name..'(ui'..recv_argv..');\n') + bridge_output:write(recv_cleanup) + bridge_output:write('}\n\n') + + bridge_output:write('static void ui_bridge_'..ev.name) + write_signature(bridge_output, ev, 'UI *ui') + bridge_output:write('\n{\n') + bridge_output:write(send) + bridge_output:write(' UI_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + end + end + + call_output:write('void ui_call_'..ev.name) + write_signature(call_output, ev, '') + call_output:write('\n{\n') + if ev.remote_only then + write_arglist(call_output, ev, false) + call_output:write(' ui_event("'..ev.name..'", args);\n') + else + call_output:write(' UI_CALL') + write_signature(call_output, ev, ev.name, true) + call_output:write(";\n") + end + call_output:write("}\n\n") + +end + +proto_output:close() +call_output:close() +remote_output:close() + +-- don't expose internal attributes like "impl_name" in public metadata +exported_attributes = {'name', 'parameters', + 'since', 'deprecated_since'} +exported_events = {} +for _,ev in ipairs(events) do + local ev_exported = {} + for _,attr in ipairs(exported_attributes) do + ev_exported[attr] = ev[attr] + end + for _,p in ipairs(ev_exported.parameters) do + if p[1] == 'HlAttrs' then + p[1] = 'Dictionary' + end + end + exported_events[#exported_events+1] = ev_exported +end + +packed = mpack.pack(exported_events) +dump_bin_array = require("generators.dump_bin_array") +dump_bin_array(metadata_output, 'ui_events_metadata', packed) +metadata_output:close() diff --git a/scripts/gencharblob.lua b/src/nvim/generators/gen_char_blob.lua index d860375e26..d860375e26 100644 --- a/scripts/gencharblob.lua +++ b/src/nvim/generators/gen_char_blob.lua diff --git a/scripts/gendeclarations.lua b/src/nvim/generators/gen_declarations.lua index e999e53e4a..e999e53e4a 100755 --- a/scripts/gendeclarations.lua +++ b/src/nvim/generators/gen_declarations.lua diff --git a/scripts/geneval.lua b/src/nvim/generators/gen_eval.lua index b17ecb1a0c..23435a1d0b 100644 --- a/scripts/geneval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -25,7 +25,7 @@ local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs local metadata = mpack.unpack(io.open(arg[3], 'rb'):read("*all")) for i,fun in ipairs(metadata) do - if not fun.noeval then + if not fun.remote_only then funcs[fun.name] = { args=#fun.parameters, func='api_wrapper', diff --git a/scripts/gen_events.lua b/src/nvim/generators/gen_events.lua index 75e0b3da3a..75e0b3da3a 100644 --- a/scripts/gen_events.lua +++ b/src/nvim/generators/gen_events.lua diff --git a/scripts/genex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index cb566d46ca..cb566d46ca 100644 --- a/scripts/genex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua diff --git a/scripts/genoptions.lua b/src/nvim/generators/gen_options.lua index 9d7f235a3b..9d7f235a3b 100644 --- a/scripts/genoptions.lua +++ b/src/nvim/generators/gen_options.lua diff --git a/scripts/genunicodetables.lua b/src/nvim/generators/gen_unicode_tables.lua index 66430ba26e..66430ba26e 100644 --- a/scripts/genunicodetables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 7cf326aef5..a7d5af36a1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -373,6 +373,46 @@ static int nlua_eval_lua_string(lua_State *const lstate) return 0; } +/// Evaluate lua string +/// +/// Expects four values on the stack: string to evaluate, pointer to args array, +/// and locations where result and error are saved, respectively. Always +/// returns nothing (from the lua point of view). +static int nlua_exec_lua_string_api(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ + const String *str = (const String *)lua_touserdata(lstate, 1); + const Array *args = (const Array *)lua_touserdata(lstate, 2); + Object *retval = (Object *)lua_touserdata(lstate, 3); + Error *err = (Error *)lua_touserdata(lstate, 4); + + lua_pop(lstate, 4); + + if (luaL_loadbuffer(lstate, str->data, str->size, "<nvim>")) { + size_t len; + const char *str = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeValidation, + "Error loading lua: %.*s", (int)len, str); + return 0; + } + + for (size_t i = 0; i < args->size; i++) { + nlua_push_Object(lstate, args->items[i]); + } + + if (lua_pcall(lstate, (int)args->size, 1, 0)) { + size_t len; + const char *str = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, str); + return 0; + } + + *retval = nlua_pop_Object(lstate, err); + + return 0; +} + /// Print as a Vim message /// /// @param lstate Lua interpreter state. @@ -516,6 +556,28 @@ void executor_eval_lua(const String str, typval_T *const arg, (void *)&str, arg, ret_tv); } +/// Execute lua string +/// +/// Used for nvim_execute_lua(). +/// +/// @param[in] str String to execute. +/// @param[in] args array of ... args +/// @param[out] err Location where error will be saved. +/// +/// @return Return value of the execution. +Object executor_exec_lua_api(const String str, const Array args, Error *err) +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + Object retval = NIL; + NLUA_CALL_C_FUNCTION_4(global_lstate, nlua_exec_lua_string_api, 0, + (void *)&str, (void *)&args, &retval, err); + return retval; +} + + /// Run lua string /// /// Used for :lua. diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 214af82422..9ab6dc5d2b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -153,4 +153,22 @@ #define STR_(x) #x #define STR(x) STR_(x) +#ifndef __has_attribute +# define NVIM_HAS_ATTRIBUTE(x) 0 +#elif defined(__clang__) && __clang__ == 1 \ + && (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ <= 5)) +// Starting in Clang 3.6, __has_attribute was fixed to only report true for +// GNU-style attributes. Prior to that, it reported true if _any_ backend +// supported the attribute. +# define NVIM_HAS_ATTRIBUTE(x) 0 +#else +# define NVIM_HAS_ATTRIBUTE __has_attribute +#endif + +#if NVIM_HAS_ATTRIBUTE(fallthrough) +# define FALLTHROUGH __attribute__((fallthrough)) +#else +# define FALLTHROUGH +#endif + #endif // NVIM_MACROS_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 1095d4d3b5..40b553e93c 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -1303,8 +1303,8 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct((char_u *)"ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, p_ef, p_efm, TRUE, IObuff) < 0) { - ui_putc('\n'); + if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { + ui_linefeed(); mch_exit(3); } TIME_MSG("reading errorfile"); diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 4e621b49c1..7e9e9e9e5c 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -247,7 +247,7 @@ ex_menu ( xfree(map_buf); } - ui_update_menu(); + ui_call_update_menu(); theend: ; diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 6de74fddf2..9630656f3f 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2536,9 +2536,9 @@ void vim_beep(unsigned val) if (emsg_silent == 0) { if (!((bo_flags & val) || (bo_flags & BO_ALL))) { if (p_vb) { - ui_visual_bell(); + ui_call_visual_bell(); } else { - ui_putc(BELL); + ui_call_bell(); } } @@ -2691,7 +2691,7 @@ int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) if (p_verbose > 3) { verbose_enter(); smsg(_("Calling shell to execute: \"%s\""), cmd == NULL ? p_sh : cmd); - ui_putc('\n'); + ui_linefeed(); verbose_leave(); } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index b1c146d406..d908a022f1 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -479,9 +479,9 @@ void setmouse(void) checkfor = MOUSE_NORMAL; /* assume normal mode */ if (mouse_has(checkfor)) { - ui_mouse_on(); + ui_call_mouse_on(); } else { - ui_mouse_off(); + ui_call_mouse_off(); } } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f73e3079b9..050020d79d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1861,6 +1861,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { bangredo = true; // do_bang() will put cmd in redo buffer. } + // fallthrough case OP_INDENT: case OP_COLON: diff --git a/src/nvim/option.c b/src/nvim/option.c index ad09b936e0..9b4cd0924b 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1952,15 +1952,7 @@ did_set_title ( { if (starting != NO_SCREEN) { maketitle(); - if (icon) { - if (!p_icon) { - ui_set_icon(NULL); - } - } else { - if (!p_title) { - ui_set_title(NULL); - } - } + resettitle(); } } @@ -3282,7 +3274,7 @@ did_set_string_option ( if (varp == &p_mouse) { if (*p_mouse == NUL) { - ui_mouse_off(); + ui_call_mouse_off(); } else { setmouse(); // in case 'mouse' changed } diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index fc8ab7dc8f..1a40309655 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -464,9 +464,10 @@ static void out_data_append_to_screen(char *output, size_t remaining, continue; } - // Translate NUL to SOH - if (output[off] == NUL) { - output[off] = 1; + // TODO(bfredl): using msg_puts would be better until + // terminal emulation is implemented. + if (output[off] < 0x20) { + output[off] = ' '; } off++; diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index c5a42204be..7cf0d7817c 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -133,7 +133,7 @@ void mch_free_acl(vim_acl_T aclent) } #endif -void mch_exit(int r) +void mch_exit(int r) FUNC_ATTR_NORETURN { exiting = true; diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index b8650d8c62..9f3abfcb89 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -98,7 +98,6 @@ redo: } if (pum_external) { - Array args = ARRAY_DICT_INIT; if (array_changed) { Array arr = ARRAY_DICT_INIT; for (i = 0; i < size; i++) { @@ -109,14 +108,9 @@ redo: ADD(item, STRING_OBJ(cstr_to_string((char *)array[i].pum_info))); ADD(arr, ARRAY_OBJ(item)); } - ADD(args, ARRAY_OBJ(arr)); - ADD(args, INTEGER_OBJ(selected)); - ADD(args, INTEGER_OBJ(row)); - ADD(args, INTEGER_OBJ(col)); - ui_event("popupmenu_show", args); + ui_call_popupmenu_show(arr, selected, row, col); } else { - ADD(args, INTEGER_OBJ(selected)); - ui_event("popupmenu_select", args); + ui_call_popupmenu_select(selected); } return; } @@ -713,8 +707,7 @@ void pum_undisplay(void) pum_array = NULL; if (pum_external) { - Array args = ARRAY_DICT_INIT; - ui_event("popupmenu_hide", args); + ui_call_popupmenu_hide(); } else { redraw_all_later(SOME_VALID); redraw_tabline = true; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 85fae9d82e..24c156d2ba 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -634,6 +634,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_o7; break; } + return FAIL; case 'a': if (*(p + 2) == 'z') { config |= CLASS_az; @@ -642,6 +643,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_af; break; } + return FAIL; case 'A': if (*(p + 2) == 'Z') { config |= CLASS_AZ; @@ -650,7 +652,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_AF; break; } - /* FALLTHROUGH */ + return FAIL; default: return FAIL; } @@ -4194,6 +4196,7 @@ skip_add: subs = addstate(l, state->out, subs, pim, off_arg); break; } + // fallthrough case NFA_MCLOSE1: case NFA_MCLOSE2: case NFA_MCLOSE3: diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 238b3f07c8..4f4363d121 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -5956,7 +5956,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, col = off - LineOffset[row]; screen_stop_highlight(); ui_cursor_goto(row, col); // clear rest of this screen line - ui_eol_clear(); + ui_call_eol_clear(); col = end_col - col; while (col--) { /* clear chars in ScreenLines */ ScreenLines[off] = ' '; @@ -6345,10 +6345,10 @@ static void screenclear2(void) LineWraps[i] = FALSE; } - ui_clear(); // clear the display - clear_cmdline = FALSE; - mode_displayed = FALSE; - screen_cleared = TRUE; /* can use contents of ScreenLines now */ + ui_call_clear(); // clear the display + clear_cmdline = false; + mode_displayed = false; + screen_cleared = true; // can use contents of ScreenLines now win_rest_invalid(firstwin); redraw_cmdline = TRUE; @@ -6647,7 +6647,7 @@ int screen_ins_lines ( } } - ui_append_lines(line_count); + ui_call_scroll(-line_count); return OK; } @@ -6702,7 +6702,7 @@ int screen_del_lines ( } } - ui_delete_lines(line_count); + ui_call_scroll(line_count); return OK; } @@ -7087,8 +7087,6 @@ static void draw_tabline(void) void ui_ext_tabline_update(void) { - Array args = ARRAY_DICT_INIT; - ADD(args, TABPAGE_OBJ(curtab->handle)); Array tabs = ARRAY_DICT_INIT; FOR_ALL_TABS(tp) { Dictionary tab_info = ARRAY_DICT_INIT; @@ -7100,9 +7098,7 @@ void ui_ext_tabline_update(void) ADD(tabs, DICTIONARY_OBJ(tab_info)); } - ADD(args, ARRAY_OBJ(tabs)); - - ui_event("tabline_update", args); + ui_call_tabline_update(curtab->handle, tabs); } /* diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a6d8cb6563..e1879ca8c0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2047,7 +2047,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDReadStatusNotShaDa: { ret = kSDWriteReadNotShada; - // fallthrough + FALLTHROUGH; } case kSDReadStatusReadError: { return ret; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index cc1de583d3..b8b7085c5e 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -683,7 +683,7 @@ static int term_settermprop(VTermProp prop, VTermValue *val, void *data) static int term_bell(void *data) { - ui_putc('\x07'); + ui_call_bell(); return 1; } diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 64ad0f0103..6832622cdf 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -134,7 +134,7 @@ else endif " Names of flaky tests. -let s:flaky = ['Test_with_partial_callback'] +let s:flaky = ['Test_with_partial_callback()'] " Locate Test_ functions and execute them. set nomore diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 399d6c74ca..4a81b32199 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -187,7 +187,7 @@ static void terminfo_stop(UI *ui) { TUIData *data = ui->data; // Destroy output stuff - tui_mode_change(ui, SHAPE_IDX_N); + tui_mode_change(ui, (String)STRING_INIT, SHAPE_IDX_N); tui_mouse_off(ui); unibi_out(ui, unibi_exit_attribute_mode); // cursor should be set to normal before exiting alternate screen @@ -417,14 +417,14 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) unibi_goto(ui, grid->row, grid->col); } -static void tui_resize(UI *ui, int width, int height) +static void tui_resize(UI *ui, Integer width, Integer height) { TUIData *data = ui->data; - ugrid_resize(&data->grid, width, height); + ugrid_resize(&data->grid, (int)width, (int)height); if (!got_winch) { // Try to resize the terminal window. char r[16]; // enough for 9999x9999 - snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width); + snprintf(r, sizeof(r), "\x1b[8;%d;%dt", (int)height, (int)width); out(ui, r, strlen(r)); } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; @@ -447,11 +447,11 @@ static void tui_eol_clear(UI *ui) clear_region(ui, grid->row, grid->row, grid->col, grid->right); } -static void tui_cursor_goto(UI *ui, int row, int col) +static void tui_cursor_goto(UI *ui, Integer row, Integer col) { TUIData *data = ui->data; - ugrid_goto(&data->grid, row, col); - unibi_goto(ui, row, col); + ugrid_goto(&data->grid, (int)row, (int)col); + unibi_goto(ui, (int)row, (int)col); } CursorShape tui_cursor_decode_shape(const char *shape_str) @@ -599,30 +599,31 @@ static void tui_set_mode(UI *ui, ModeShape mode) } /// @param mode editor mode -static void tui_mode_change(UI *ui, int mode_idx) +static void tui_mode_change(UI *ui, String mode, Integer mode_idx) { TUIData *data = ui->data; tui_set_mode(ui, (ModeShape)mode_idx); data->showing_mode = (ModeShape)mode_idx; } -static void tui_set_scroll_region(UI *ui, int top, int bot, int left, - int right) +static void tui_set_scroll_region(UI *ui, Integer top, Integer bot, + Integer left, Integer right) { TUIData *data = ui->data; - ugrid_set_scroll_region(&data->grid, top, bot, left, right); + ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, + (int)left, (int)right); data->can_use_terminal_scroll = left == 0 && right == ui->width - 1 && ((top == 0 && bot == ui->height - 1) || unibi_get_str(data->ut, unibi_change_scroll_region)); } -static void tui_scroll(UI *ui, int count) +static void tui_scroll(UI *ui, Integer count) { TUIData *data = ui->data; UGrid *grid = &data->grid; int clear_top, clear_bot; - ugrid_scroll(grid, count, &clear_top, &clear_bot); + ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); if (data->can_use_terminal_scroll) { // Change terminal scroll region and move cursor to the top @@ -642,7 +643,7 @@ static void tui_scroll(UI *ui, int count) if (count == 1) { unibi_out(ui, unibi_delete_line); } else { - data->params[0].i = count; + data->params[0].i = (int)count; unibi_out(ui, unibi_parm_delete_line); } } @@ -652,7 +653,7 @@ static void tui_scroll(UI *ui, int count) if (count == -1) { unibi_out(ui, unibi_insert_line); } else { - data->params[0].i = -count; + data->params[0].i = -(int)count; unibi_out(ui, unibi_parm_insert_line); } } @@ -683,10 +684,10 @@ static void tui_highlight_set(UI *ui, HlAttrs attrs) ((TUIData *)ui->data)->grid.attrs = attrs; } -static void tui_put(UI *ui, uint8_t *text, size_t size) +static void tui_put(UI *ui, String text) { TUIData *data = ui->data; - print_cell(ui, ugrid_put(&data->grid, text, size)); + print_cell(ui, ugrid_put(&data->grid, (uint8_t *)text.data, text.size)); } static void tui_bell(UI *ui) @@ -699,17 +700,17 @@ static void tui_visual_bell(UI *ui) unibi_out(ui, unibi_flash_screen); } -static void tui_update_fg(UI *ui, int fg) +static void tui_update_fg(UI *ui, Integer fg) { - ((TUIData *)ui->data)->grid.fg = fg; + ((TUIData *)ui->data)->grid.fg = (int)fg; } -static void tui_update_bg(UI *ui, int bg) +static void tui_update_bg(UI *ui, Integer bg) { - ((TUIData *)ui->data)->grid.bg = bg; + ((TUIData *)ui->data)->grid.bg = (int)bg; } -static void tui_update_sp(UI *ui, int sp) +static void tui_update_sp(UI *ui, Integer sp) { // Do nothing; 'special' color is for GUI only } @@ -784,19 +785,19 @@ static void tui_suspend(UI *ui) #endif } -static void tui_set_title(UI *ui, char *title) +static void tui_set_title(UI *ui, String title) { TUIData *data = ui->data; - if (!(title && unibi_get_str(data->ut, unibi_to_status_line) + if (!(title.data && unibi_get_str(data->ut, unibi_to_status_line) && unibi_get_str(data->ut, unibi_from_status_line))) { return; } unibi_out(ui, unibi_to_status_line); - out(ui, title, strlen(title)); + out(ui, title.data, title.size); unibi_out(ui, unibi_from_status_line); } -static void tui_set_icon(UI *ui, char *icon) +static void tui_set_icon(UI *ui, String icon) { } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 924a4192bc..0a2154438f 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -89,6 +89,10 @@ static int old_mode_idx = -1; #define UI_CALL_MORE(method, ...) if (ui->method) ui->method(ui, __VA_ARGS__) #define UI_CALL_ZERO(method) if (ui->method) ui->method(ui) +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_events_call.generated.h" +#endif + void ui_builtin_start(void) { #ifdef FEAT_TUI @@ -128,24 +132,6 @@ bool ui_active(void) return ui_count != 0; } -void ui_suspend(void) -{ - UI_CALL(suspend); - UI_CALL(flush); -} - -void ui_set_title(char *title) -{ - UI_CALL(set_title, title); - UI_CALL(flush); -} - -void ui_set_icon(char *icon) -{ - UI_CALL(set_icon, icon); - UI_CALL(flush); -} - void ui_event(char *name, Array args) { bool args_consumed = false; @@ -214,33 +200,23 @@ void ui_resize(int new_width, int new_height) sr.bot = height - 1; sr.left = 0; sr.right = width - 1; - UI_CALL(resize, width, height); + ui_call_resize(width, height); } void ui_busy_start(void) { if (!(busy++)) { - UI_CALL(busy_start); + ui_call_busy_start(); } } void ui_busy_stop(void) { if (!(--busy)) { - UI_CALL(busy_stop); + ui_call_busy_stop(); } } -void ui_mouse_on(void) -{ - UI_CALL(mouse_on); -} - -void ui_mouse_off(void) -{ - UI_CALL(mouse_off); -} - void ui_attach_impl(UI *ui) { if (ui_count == MAX_UI_COUNT) { @@ -281,11 +257,6 @@ void ui_detach_impl(UI *ui) } } -void ui_clear(void) -{ - UI_CALL(clear); -} - // Set scrolling region for window 'wp'. // The region starts 'off' lines from the start of the window. // Also set the vertical scroll region for a vertically split window. Always @@ -300,7 +271,7 @@ void ui_set_scroll_region(win_T *wp, int off) sr.right = wp->w_wincol + wp->w_width - 1; } - UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right); + ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); } // Reset scrolling region to the whole screen. @@ -310,22 +281,7 @@ void ui_reset_scroll_region(void) sr.bot = (int)Rows - 1; sr.left = 0; sr.right = (int)Columns - 1; - UI_CALL(set_scroll_region, sr.top, sr.bot, sr.left, sr.right); -} - -void ui_append_lines(int count) -{ - UI_CALL(scroll, -count); -} - -void ui_delete_lines(int count) -{ - UI_CALL(scroll, count); -} - -void ui_eol_clear(void) -{ - UI_CALL(eol_clear); + ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); } void ui_start_highlight(int attr_code) @@ -350,23 +306,31 @@ void ui_stop_highlight(void) set_highlight_args(current_attr_code); } -void ui_visual_bell(void) -{ - UI_CALL(visual_bell); -} - void ui_puts(uint8_t *str) { - uint8_t *ptr = str; + uint8_t *p = str; uint8_t c; - while ((c = *ptr)) { + while ((c = *p)) { if (c < 0x20) { - parse_control_character(c); - ptr++; - } else { - send_output(&ptr); + abort(); + } + + size_t clen = (size_t)mb_ptr2len(p); + ui_call_put((String){ .data = (char *)p, .size = clen }); + col++; + if (mb_ptr2cells(p) > 1) { + // double cell character, blank the next cell + ui_call_put((String)STRING_INIT); + col++; } + if (utf_ambiguous_width(utf_ptr2char(p))) { + pending_cursor_update = true; + } + if (col >= width) { + ui_linefeed(); + } + p += clen; } } @@ -390,15 +354,10 @@ void ui_mode_info_set(void) { Array style = mode_style_array(); bool enabled = (*p_guicursor != NUL); - UI_CALL(mode_info_set, enabled, style); + ui_call_mode_info_set(enabled, style); api_free_array(style); } -void ui_update_menu(void) -{ - UI_CALL(update_menu); -} - int ui_current_row(void) { return row; @@ -411,47 +370,7 @@ int ui_current_col(void) void ui_flush(void) { - UI_CALL(flush); -} - -static void send_output(uint8_t **ptr) -{ - uint8_t *p = *ptr; - - while (*p >= 0x20) { - size_t clen = (size_t)mb_ptr2len(p); - UI_CALL(put, p, (size_t)clen); - col++; - if (mb_ptr2cells(p) > 1) { - // double cell character, blank the next cell - UI_CALL(put, NULL, 0); - col++; - } - if (utf_ambiguous_width(utf_ptr2char(p))) { - pending_cursor_update = true; - } - if (col >= width) { - ui_linefeed(); - } - p += clen; - } - - *ptr = p; -} - -static void parse_control_character(uint8_t c) -{ - if (c == '\n') { - ui_linefeed(); - } else if (c == '\r') { - ui_carriage_return(); - } else if (c == '\b') { - ui_cursor_left(); - } else if (c == Ctrl_L) { - ui_cursor_right(); - } else if (c == Ctrl_G) { - UI_CALL(bell); - } + ui_call_flush(); } static void set_highlight_args(int attr_code) @@ -509,43 +428,23 @@ end: UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); } -static void ui_linefeed(void) +void ui_linefeed(void) { int new_col = 0; int new_row = row; if (new_row < sr.bot) { new_row++; } else { - UI_CALL(scroll, 1); + ui_call_scroll(1); } ui_cursor_goto(new_row, new_col); } -static void ui_carriage_return(void) -{ - int new_col = 0; - ui_cursor_goto(row, new_col); -} - -static void ui_cursor_left(void) -{ - int new_col = col - 1; - assert(new_col >= 0); - ui_cursor_goto(row, new_col); -} - -static void ui_cursor_right(void) -{ - int new_col = col + 1; - assert(new_col < width); - ui_cursor_goto(row, new_col); -} - static void flush_cursor_update(void) { if (pending_cursor_update) { pending_cursor_update = false; - UI_CALL(cursor_goto, row, col); + ui_call_cursor_goto(row, col); } } @@ -560,7 +459,8 @@ void ui_cursor_shape(void) if (old_mode_idx != mode_idx) { old_mode_idx = mode_idx; - UI_CALL(mode_change, mode_idx); + char *full_name = shape_table[mode_idx].full_name; + ui_call_mode_change(cstr_as_string(full_name), mode_idx); } conceal_check_cursur_line(); } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 9338ab3ea3..064f77fee1 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -28,35 +28,17 @@ struct ui_t { bool ui_ext[UI_WIDGETS]; ///< Externalized widgets int width, height; void *data; - void (*resize)(UI *ui, int rows, int columns); - void (*clear)(UI *ui); - void (*eol_clear)(UI *ui); - void (*cursor_goto)(UI *ui, int row, int col); - void (*mode_info_set)(UI *ui, bool enabled, Array cursor_styles); - void (*update_menu)(UI *ui); - void (*busy_start)(UI *ui); - void (*busy_stop)(UI *ui); - void (*mouse_on)(UI *ui); - void (*mouse_off)(UI *ui); - void (*mode_change)(UI *ui, int mode_idx); - void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right); - void (*scroll)(UI *ui, int count); - void (*highlight_set)(UI *ui, HlAttrs attrs); - void (*put)(UI *ui, uint8_t *str, size_t len); - void (*bell)(UI *ui); - void (*visual_bell)(UI *ui); - void (*flush)(UI *ui); - void (*update_fg)(UI *ui, int fg); - void (*update_bg)(UI *ui, int bg); - void (*update_sp)(UI *ui, int sp); - void (*suspend)(UI *ui); - void (*set_title)(UI *ui, char *title); - void (*set_icon)(UI *ui, char *icon); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_events.generated.h" +#endif + void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); }; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui.h.generated.h" +# include "ui_events_call.h.generated.h" #endif #endif // NVIM_UI_H diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index d790770892..0165db7c0c 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -49,8 +49,12 @@ static argv_callback uilog_event = NULL; event_create(ui_bridge_##name##_event, argc, __VA_ARGS__), UI(ui)) #endif -#define INT2PTR(i) ((void *)(uintptr_t)i) -#define PTR2INT(p) ((int)(uintptr_t)p) +#define INT2PTR(i) ((void *)(intptr_t)i) +#define PTR2INT(p) ((Integer)(intptr_t)p) + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "ui_events_bridge.generated.h" +#endif UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) { @@ -146,148 +150,6 @@ static void ui_bridge_stop_event(void **argv) ui->stop(ui); } -static void ui_bridge_resize(UI *b, int width, int height) -{ - UI_CALL(b, resize, 3, b, INT2PTR(width), INT2PTR(height)); -} -static void ui_bridge_resize_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->resize(ui, PTR2INT(argv[1]), PTR2INT(argv[2])); -} - -static void ui_bridge_clear(UI *b) -{ - UI_CALL(b, clear, 1, b); -} -static void ui_bridge_clear_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->clear(ui); -} - -static void ui_bridge_eol_clear(UI *b) -{ - UI_CALL(b, eol_clear, 1, b); -} -static void ui_bridge_eol_clear_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->eol_clear(ui); -} - -static void ui_bridge_cursor_goto(UI *b, int row, int col) -{ - UI_CALL(b, cursor_goto, 3, b, INT2PTR(row), INT2PTR(col)); -} -static void ui_bridge_cursor_goto_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->cursor_goto(ui, PTR2INT(argv[1]), PTR2INT(argv[2])); -} - -static void ui_bridge_mode_info_set(UI *b, bool enabled, Array modes) -{ - bool *enabledp = xmalloc(sizeof(*enabledp)); - Object *modesp = xmalloc(sizeof(*modesp)); - *enabledp = enabled; - *modesp = copy_object(ARRAY_OBJ(modes)); - UI_CALL(b, mode_info_set, 3, b, enabledp, modesp); -} -static void ui_bridge_mode_info_set_event(void **argv) -{ - UI *ui = UI(argv[0]); - bool *enabled = argv[1]; - Object *modes = argv[2]; - ui->mode_info_set(ui, *enabled, modes->data.array); - xfree(enabled); - api_free_object(*modes); - xfree(modes); -} - -static void ui_bridge_update_menu(UI *b) -{ - UI_CALL(b, update_menu, 1, b); -} -static void ui_bridge_update_menu_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->update_menu(ui); -} - -static void ui_bridge_busy_start(UI *b) -{ - UI_CALL(b, busy_start, 1, b); -} -static void ui_bridge_busy_start_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->busy_start(ui); -} - -static void ui_bridge_busy_stop(UI *b) -{ - UI_CALL(b, busy_stop, 1, b); -} -static void ui_bridge_busy_stop_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->busy_stop(ui); -} - -static void ui_bridge_mouse_on(UI *b) -{ - UI_CALL(b, mouse_on, 1, b); -} -static void ui_bridge_mouse_on_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->mouse_on(ui); -} - -static void ui_bridge_mouse_off(UI *b) -{ - UI_CALL(b, mouse_off, 1, b); -} -static void ui_bridge_mouse_off_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->mouse_off(ui); -} - -static void ui_bridge_mode_change(UI *b, int mode_idx) -{ - UI_CALL(b, mode_change, 2, b, INT2PTR(mode_idx)); -} -static void ui_bridge_mode_change_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->mode_change(ui, PTR2INT(argv[1])); -} - -static void ui_bridge_set_scroll_region(UI *b, int top, int bot, int left, - int right) -{ - UI_CALL(b, set_scroll_region, 5, b, INT2PTR(top), INT2PTR(bot), - INT2PTR(left), INT2PTR(right)); -} -static void ui_bridge_set_scroll_region_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->set_scroll_region(ui, PTR2INT(argv[1]), PTR2INT(argv[2]), - PTR2INT(argv[3]), PTR2INT(argv[4])); -} - -static void ui_bridge_scroll(UI *b, int count) -{ - UI_CALL(b, scroll, 2, b, INT2PTR(count)); -} -static void ui_bridge_scroll_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->scroll(ui, PTR2INT(argv[1])); -} - static void ui_bridge_highlight_set(UI *b, HlAttrs attrs) { HlAttrs *a = xmalloc(sizeof(HlAttrs)); @@ -301,82 +163,6 @@ static void ui_bridge_highlight_set_event(void **argv) xfree(argv[1]); } -static void ui_bridge_put(UI *b, uint8_t *text, size_t size) -{ - uint8_t *t = NULL; - if (text) { - t = xmalloc(sizeof(((UCell *)0)->data)); - memcpy(t, text, size); - } - UI_CALL(b, put, 3, b, t, INT2PTR(size)); -} -static void ui_bridge_put_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->put(ui, (uint8_t *)argv[1], (size_t)(uintptr_t)argv[2]); - xfree(argv[1]); -} - -static void ui_bridge_bell(UI *b) -{ - UI_CALL(b, bell, 1, b); -} -static void ui_bridge_bell_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->bell(ui); -} - -static void ui_bridge_visual_bell(UI *b) -{ - UI_CALL(b, visual_bell, 1, b); -} -static void ui_bridge_visual_bell_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->visual_bell(ui); -} - -static void ui_bridge_update_fg(UI *b, int fg) -{ - UI_CALL(b, update_fg, 2, b, INT2PTR(fg)); -} -static void ui_bridge_update_fg_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->update_fg(ui, PTR2INT(argv[1])); -} - -static void ui_bridge_update_bg(UI *b, int bg) -{ - UI_CALL(b, update_bg, 2, b, INT2PTR(bg)); -} -static void ui_bridge_update_bg_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->update_bg(ui, PTR2INT(argv[1])); -} - -static void ui_bridge_update_sp(UI *b, int sp) -{ - UI_CALL(b, update_sp, 2, b, INT2PTR(sp)); -} -static void ui_bridge_update_sp_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->update_sp(ui, PTR2INT(argv[1])); -} - -static void ui_bridge_flush(UI *b) -{ - UI_CALL(b, flush, 1, b); -} -static void ui_bridge_flush_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->flush(ui); -} - static void ui_bridge_suspend(UI *b) { UIBridgeData *data = (UIBridgeData *)b; @@ -394,25 +180,3 @@ static void ui_bridge_suspend_event(void **argv) UI *ui = UI(argv[0]); ui->suspend(ui); } - -static void ui_bridge_set_title(UI *b, char *title) -{ - UI_CALL(b, set_title, 2, b, title ? xstrdup(title) : NULL); -} -static void ui_bridge_set_title_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->set_title(ui, argv[1]); - xfree(argv[1]); -} - -static void ui_bridge_set_icon(UI *b, char *icon) -{ - UI_CALL(b, set_icon, 2, b, icon ? xstrdup(icon) : NULL); -} -static void ui_bridge_set_icon_event(void **argv) -{ - UI *ui = UI(argv[0]); - ui->set_icon(ui, argv[1]); - xfree(argv[1]); -} diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 282ecbfd87..61ec6ea829 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -81,6 +81,36 @@ describe('api', function() end) end) + describe('nvim_execute_lua', function() + it('works', function() + meths.execute_lua('vim.api.nvim_set_var("test", 3)', {}) + eq(3, meths.get_var('test')) + + eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7})) + + eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{})) + eq("xy", meths.execute_lua('return xx(...)', {'x','y'})) + end) + + it('reports errors', function() + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "'=' expected near '+'"}, + meth_pcall(meths.execute_lua, 'a+*b', {})) + + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "unexpected symbol near '1'"}, + meth_pcall(meths.execute_lua, '1+2', {})) + + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "unexpected symbol"}, + meth_pcall(meths.execute_lua, 'aa=bb\0', {})) + + eq({false, 'Error executing lua: [string "<nvim>"]:1: '.. + "attempt to call global 'bork' (a nil value)"}, + meth_pcall(meths.execute_lua, 'bork()', {})) + end) + end) + describe('nvim_input', function() it("VimL error: does NOT fail, updates v:errmsg", function() local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>") diff --git a/test/functional/eval/api_functions_spec.lua b/test/functional/eval/api_functions_spec.lua index 7f6f53d226..fea4a87a26 100644 --- a/test/functional/eval/api_functions_spec.lua +++ b/test/functional/eval/api_functions_spec.lua @@ -106,7 +106,7 @@ describe('api functions', function() it('have metadata accessible with api_info()', function() local api_keys = eval("sort(keys(api_info()))") - eq({'error_types', 'functions', 'types', 'version'}, api_keys) + eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_keys) end) it('are highlighted by vim.vim syntax file', function() diff --git a/test/functional/eval/msgpack_functions_spec.lua b/test/functional/eval/msgpack_functions_spec.lua index c5520deb73..4a052b4aff 100644 --- a/test/functional/eval/msgpack_functions_spec.lua +++ b/test/functional/eval/msgpack_functions_spec.lua @@ -461,7 +461,7 @@ describe('msgpackparse() function', function() eval(cmd) eval(cmd) -- do it again (try to force segfault) local api_info = eval(cmd) -- do it again - eq({'error_types', 'functions', 'types', 'version'}, api_info) + eq({'error_types', 'functions', 'types', 'ui_events', 'version'}, api_info) end) it('fails when called with no arguments', function() diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua new file mode 100644 index 0000000000..4e57d2755d --- /dev/null +++ b/test/functional/ex_cmds/script_spec.lua @@ -0,0 +1,75 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local neq = helpers.neq +local meths = helpers.meths +local clear = helpers.clear +local dedent = helpers.dedent +local source = helpers.source +local exc_exec = helpers.exc_exec +local missing_provider = helpers.missing_provider + +before_each(clear) + +describe('script_get-based command', function() + local garbage = ')}{+*({}]*[;(+}{&[]}{*])(' + + local function test_garbage_exec(cmd, check_neq) + describe(cmd, function() + it('works correctly when skipping oneline variant', function() + eq(true, pcall(source, (dedent([[ + if 0 + %s %s + endif + ]])):format(cmd, garbage))) + eq('', meths.command_output('messages')) + if check_neq then + neq(0, exc_exec(dedent([[ + %s %s + ]])):format(cmd, garbage)) + end + end) + it('works correctly when skipping HEREdoc variant', function() + eq(true, pcall(source, (dedent([[ + if 0 + %s << EOF + %s + EOF + endif + ]])):format(cmd, garbage))) + eq('', meths.command_output('messages')) + if check_neq then + eq(true, pcall(source, (dedent([[ + let g:exc = 0 + try + %s << EOF + %s + EOF + catch + let g:exc = v:exception + endtry + ]])):format(cmd, garbage))) + neq(0, meths.get_var('exc')) + end + end) + end) + end + + clear() + + -- Built-in scripts + test_garbage_exec('lua', true) + + -- Provider-based scripts + test_garbage_exec('ruby', not missing_provider('ruby')) + test_garbage_exec('python', not missing_provider('python')) + test_garbage_exec('python3', not missing_provider('python3')) + + -- Missing scripts + test_garbage_exec('tcl', false) + test_garbage_exec('mzscheme', false) + test_garbage_exec('perl', false) + + -- Not really a script + test_garbage_exec('xxxinvalidlanguagexxx', true) +end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 1be70f917c..b03840b3fe 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -566,6 +566,19 @@ local function get_pathsep() return funcs.fnamemodify('.', ':p'):sub(-1) end +local function missing_provider(provider) + if provider == 'ruby' then + local prog = funcs['provider#' .. provider .. '#Detect']() + return prog == '' and (provider .. ' not detected') or false + elseif provider == 'python' or provider == 'python3' then + local py_major_version = (provider == 'python3' and 3 or 2) + local errors = funcs['provider#pythonx#Detect'](py_major_version)[2] + return errors ~= '' and errors or false + else + assert(false, 'Unknown provider: ' .. provider) + end +end + local module = { prepend_argv = prepend_argv, clear = clear, @@ -632,6 +645,7 @@ local module = { meth_pcall = meth_pcall, NIL = mpack.NIL, get_pathsep = get_pathsep, + missing_provider = missing_provider, } return function(after_each) diff --git a/test/functional/legacy/036_regexp_character_classes_spec.lua b/test/functional/legacy/036_regexp_character_classes_spec.lua index 110f7dd852..38e8145d1c 100644 --- a/test/functional/legacy/036_regexp_character_classes_spec.lua +++ b/test/functional/legacy/036_regexp_character_classes_spec.lua @@ -275,4 +275,16 @@ describe('character classes in regexp', function() diff(sixlines(string.sub(punct1, 1)..digits..punct2..upper..punct3.. lower..punct4..ctrl2..iso_text)) end) + it('does not convert character class ranges to an incorrect class', function() + source([[ + 1 s/\%#=0[0-z]//g + 2 s/\%#=1[0-z]//g + 3 s/\%#=2[0-z]//g + 4 s/\%#=0[^0-z]//g + 5 s/\%#=1[^0-z]//g + 6 s/\%#=2[^0-z]//g + ]]) + diff(string.rep(ctrl1..punct1..punct4..ctrl2..iso_text..'\n', 3) + ..string.rep(digits..punct2..upper..punct3..lower..'\n', 3)) + end) end) diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index 89a546675f..aa50f53451 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -3,14 +3,14 @@ local eval, command, feed = helpers.eval, helpers.command, helpers.feed local eq, clear, insert = helpers.eq, helpers.clear, helpers.insert local expect, write_file = helpers.expect, helpers.write_file local feed_command = helpers.feed_command +local missing_provider = helpers.missing_provider do clear() - command('let [g:interp, g:errors] = provider#pythonx#Detect(3)') - local errors = eval('g:errors') - if errors ~= '' then + local err = missing_provider('python3') + if err then pending( - 'Python 3 (or the Python 3 neovim module) is broken or missing:\n' .. errors, + 'Python 3 (or the Python 3 neovim module) is broken or missing:\n' .. err, function() end) return end diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua index 94dfa90ea8..25f5e0a6d0 100644 --- a/test/functional/provider/python_spec.lua +++ b/test/functional/provider/python_spec.lua @@ -12,14 +12,14 @@ local command = helpers.command local exc_exec = helpers.exc_exec local write_file = helpers.write_file local curbufmeths = helpers.curbufmeths +local missing_provider = helpers.missing_provider do clear() - command('let [g:interp, g:errors] = provider#pythonx#Detect(2)') - local errors = meths.get_var('errors') - if errors ~= '' then + local err = missing_provider('python') + if err then pending( - 'Python 2 (or the Python 2 neovim module) is broken or missing:\n' .. errors, + 'Python 2 (or the Python 2 neovim module) is broken or missing:\n' .. err, function() end) return end diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index 7b0e17688d..9f5ef3b3fc 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -10,13 +10,11 @@ local expect = helpers.expect local command = helpers.command local write_file = helpers.write_file local curbufmeths = helpers.curbufmeths +local missing_provider = helpers.missing_provider do clear() - command('let g:prog = provider#ruby#Detect()') - local prog = meths.get_var('prog') - - if prog == '' then + if missing_provider('ruby') then pending( "Cannot find the neovim RubyGem. Try :CheckHealth", function() end) |