diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/nvim/CMakeLists.txt | 78 | ||||
-rw-r--r-- | src/nvim/api/extmark.c | 12 | ||||
-rw-r--r-- | src/nvim/api/ui_events.in.h | 2 | ||||
-rw-r--r-- | src/nvim/arabic.c | 1 | ||||
-rw-r--r-- | src/nvim/cursor.c | 21 | ||||
-rw-r--r-- | src/nvim/decoration.c | 22 | ||||
-rw-r--r-- | src/nvim/drawline.c | 48 | ||||
-rw-r--r-- | src/nvim/edit.c | 91 | ||||
-rw-r--r-- | src/nvim/eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 4 | ||||
-rw-r--r-- | src/nvim/extmark.c | 33 | ||||
-rw-r--r-- | src/nvim/extmark.h | 2 | ||||
-rw-r--r-- | src/nvim/getchar.c | 22 | ||||
-rw-r--r-- | src/nvim/highlight_group.c | 140 | ||||
-rw-r--r-- | src/nvim/indent.c | 67 | ||||
-rw-r--r-- | src/nvim/macros_defs.h | 5 | ||||
-rw-r--r-- | src/nvim/mapping.c | 36 | ||||
-rw-r--r-- | src/nvim/mbyte.c | 170 | ||||
-rw-r--r-- | src/nvim/mbyte.h | 74 | ||||
-rw-r--r-- | src/nvim/mbyte_defs.h | 11 | ||||
-rw-r--r-- | src/nvim/mouse.c | 21 | ||||
-rw-r--r-- | src/nvim/msgpack_rpc/channel.c | 7 | ||||
-rw-r--r-- | src/nvim/ops.c | 132 | ||||
-rw-r--r-- | src/nvim/os/fs.c | 8 | ||||
-rw-r--r-- | src/nvim/plines.c | 541 | ||||
-rw-r--r-- | src/nvim/plines.h | 95 | ||||
-rw-r--r-- | src/nvim/po/CMakeLists.txt | 10 | ||||
-rw-r--r-- | src/nvim/tui/tui.c | 8 | ||||
-rw-r--r-- | src/nvim/ui.c | 6 |
29 files changed, 957 insertions, 711 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 98a9301da6..4f9edad6a8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -1,7 +1,11 @@ add_library(main_lib INTERFACE) -add_executable(nvim) -set_target_properties(nvim +# Internally we need to make a distinction between "nvim without runtime files" +# (nvim_bin) and "nvim with runtime files" (nvim). +add_executable(nvim_bin EXCLUDE_FROM_ALL) +set_target_properties(nvim_bin PROPERTIES OUTPUT_NAME nvim) + +set_target_properties(nvim_bin PROPERTIES EXPORT_COMPILE_COMMANDS ON ENABLE_EXPORTS TRUE) @@ -115,7 +119,7 @@ elseif(MINGW) target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO) # Enable wmain - target_link_libraries(nvim PRIVATE -municode) + target_link_libraries(nvim_bin PRIVATE -municode) elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU") if(CMAKE_C_COMPILER_VERSION VERSION_LESS 10) target_compile_options(main_lib INTERFACE -Wno-conversion) @@ -134,7 +138,7 @@ elseif(CMAKE_C_COMPILER_ID MATCHES "Clang") # workaround for clang-11 on macOS, supported on later versions if(NOT APPLE) - target_link_libraries(nvim PRIVATE -Wl,--no-undefined) + target_link_libraries(nvim_bin PRIVATE -Wl,--no-undefined) endif() endif() @@ -150,16 +154,16 @@ if(CMAKE_SYSTEM_NAME MATCHES "Windows") target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN) target_link_libraries(main_lib INTERFACE netapi32) elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") - target_link_libraries(nvim PRIVATE "-framework CoreServices") + target_link_libraries(nvim_bin PRIVATE "-framework CoreServices") # Actually export symbols - symbols may not be visible even though # ENABLE_EXPORTS is set to true. See # https://github.com/neovim/neovim/issues/25295 - target_link_options(nvim PRIVATE "-Wl,-export_dynamic") + target_link_options(nvim_bin PRIVATE "-Wl,-export_dynamic") elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD") target_link_libraries(main_lib INTERFACE pthread c++abi) elseif(CMAKE_SYSTEM_NAME STREQUAL "SunOS") - target_link_libraries(nvim PRIVATE -lsocket) + target_link_libraries(nvim_bin PRIVATE -lsocket) endif() check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG) @@ -205,35 +209,35 @@ if(ENABLE_ASAN_UBSAN) if(NOT MSVC) if(CI_BUILD) # Try to recover from all sanitize issues so we get reports about all failures - target_compile_options(nvim PRIVATE -fsanitize-recover=all) + target_compile_options(nvim_bin PRIVATE -fsanitize-recover=all) else() - target_compile_options(nvim PRIVATE -fno-sanitize-recover=all) + target_compile_options(nvim_bin PRIVATE -fno-sanitize-recover=all) endif() - target_compile_options(nvim PRIVATE + target_compile_options(nvim_bin PRIVATE -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=undefined) endif() - target_compile_options(nvim PRIVATE -fsanitize=address) - target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined) - target_compile_definitions(nvim PRIVATE ENABLE_ASAN_UBSAN) + target_compile_options(nvim_bin PRIVATE -fsanitize=address) + target_link_libraries(nvim_bin PRIVATE -fsanitize=address -fsanitize=undefined) + target_compile_definitions(nvim_bin PRIVATE ENABLE_ASAN_UBSAN) endif() if(ENABLE_MSAN) message(STATUS "Enabling memory sanitizer for nvim.") - target_compile_options(nvim PRIVATE + target_compile_options(nvim_bin PRIVATE -fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fno-optimize-sibling-calls) - target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins) + target_link_libraries(nvim_bin PRIVATE -fsanitize=memory -fsanitize-memory-track-origins) endif() if(ENABLE_TSAN) message(STATUS "Enabling thread sanitizer for nvim.") - target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE) - target_link_libraries(nvim PRIVATE -fsanitize=thread) + target_compile_options(nvim_bin PRIVATE -fsanitize=thread -fPIE) + target_link_libraries(nvim_bin PRIVATE -fsanitize=thread) endif() option(CI_BUILD "CI, extra flags will be set" OFF) @@ -254,7 +258,7 @@ if(ENABLE_IWYU) string(APPEND iwyu_flags "-Xiwyu;--no_fwd_decls;") string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp") - set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}") + set_target_properties(nvim_bin PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}") target_compile_definitions(main_lib INTERFACE EXITFREE) endif() @@ -284,7 +288,7 @@ endif() #------------------------------------------------------------------------------- set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack) -set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/) +set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim) set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto) set(GENERATED_INCLUDES_DIR ${PROJECT_BINARY_DIR}/include) set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators) @@ -436,7 +440,7 @@ if(CI_BUILD) # TODO(bfredl): debug log level also exposes some errors with EXITFREE in ASAN build. else() # Minimize logging for release-type builds. - target_compile_definitions(nvim PRIVATE $<$<CONFIG:Debug>:NVIM_LOG_DEBUG>) + target_compile_definitions(nvim_bin PRIVATE $<$<CONFIG:Debug>:NVIM_LOG_DEBUG>) endif() if(ENABLE_ASAN_UBSAN OR ENABLE_MSAN OR ENABLE_TSAN) @@ -685,8 +689,8 @@ if(PREFER_LUA) message(STATUS "luajit not used, skipping unit tests") else() file(GLOB UNIT_TEST_FIXTURES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c) - target_sources(nvim PRIVATE ${UNIT_TEST_FIXTURES}) - target_compile_definitions(nvim PRIVATE UNIT_TESTING) + target_sources(nvim_bin PRIVATE ${UNIT_TEST_FIXTURES}) + target_compile_definitions(nvim_bin PRIVATE UNIT_TESTING) endif() target_sources(main_lib INTERFACE @@ -700,32 +704,33 @@ target_sources(main_lib INTERFACE target_sources(nlua0 PUBLIC ${NLUA0_SOURCES}) -target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv) -install_helper(TARGETS nvim) +target_link_libraries(nvim_bin PRIVATE main_lib PUBLIC libuv) +install_helper(TARGETS nvim_bin) if(MSVC) - install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) + install(FILES $<TARGET_PDB_FILE:nvim_bin> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) endif() if(ENABLE_LTO) include(CheckIPOSupported) check_ipo_supported(RESULT IPO_SUPPORTED) if(IPO_SUPPORTED) - set_target_properties(nvim PROPERTIES + set_target_properties(nvim_bin PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE) endif() endif() +add_custom_target(nvim_runtime_deps) if(WIN32) # Copy DLLs and third-party tools to bin/ and install them along with nvim - add_custom_target(nvim_runtime_deps ALL - COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + add_custom_command(TARGET nvim_runtime_deps + COMMAND ${CMAKE_COMMAND} -E ${COPY_DIRECTORY} ${PROJECT_BINARY_DIR}/windows_runtime_deps/ ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) install(DIRECTORY ${PROJECT_BINARY_DIR}/windows_runtime_deps/ DESTINATION ${CMAKE_INSTALL_BINDIR}) - add_custom_target(nvim_dll_deps DEPENDS nvim + add_custom_target(nvim_dll_deps DEPENDS nvim_bin COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps COMMAND ${CMAKE_COMMAND} -D CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH} @@ -755,22 +760,17 @@ if(WIN32) add_custom_target(external_blobs COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/external_blobs.cmake) add_dependencies(nvim_runtime_deps external_blobs) -else() - add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046. endif() file(MAKE_DIRECTORY ${BINARY_LIB_DIR}) # install treesitter parser if bundled if(EXISTS ${DEPS_PREFIX}/lib/nvim/parser) - file(GLOB TREESITTER_PARSERS CONFIGURE_DEPENDS ${DEPS_PREFIX}/lib/nvim/parser/*) - foreach(parser_lib ${TREESITTER_PARSERS}) - file(COPY ${parser_lib} DESTINATION ${BINARY_LIB_DIR}/parser) - endforeach() + add_custom_command(TARGET nvim_runtime_deps COMMAND ${CMAKE_COMMAND} -E ${COPY_DIRECTORY} ${DEPS_PREFIX}/lib/nvim/parser ${BINARY_LIB_DIR}/parser) endif() install(DIRECTORY ${BINARY_LIB_DIR} - DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/ + DESTINATION ${CMAKE_INSTALL_LIBDIR} USE_SOURCE_PERMISSIONS) if(NOT PREFER_LUA) @@ -842,7 +842,7 @@ add_glob_target( add_custom_target(copy_compile_commands COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/compile_commands.json ${PROJECT_SOURCE_DIR}/compile_commands.json) -add_dependencies(copy_compile_commands nvim) +add_dependencies(copy_compile_commands nvim_bin) add_dependencies(lintc-clang-tidy copy_compile_commands) add_dependencies(clang-analyzer copy_compile_commands) @@ -926,7 +926,7 @@ add_custom_command( OUTPUT ${MPACK_FILES} COMMAND ${PROJECT_SOURCE_DIR}/scripts/gen_vimdoc.py DEPENDS - nvim + nvim_bin ${API_SOURCES} ${LUA_SOURCES} ${VIMDOC_FILES} @@ -938,7 +938,7 @@ add_custom_command( add_custom_command( OUTPUT ${GEN_EVAL_TOUCH} COMMAND ${CMAKE_COMMAND} -E touch ${GEN_EVAL_TOUCH} - COMMAND $<TARGET_FILE:nvim> -l ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua + COMMAND $<TARGET_FILE:nvim_bin> -l ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua DEPENDS ${API_METADATA} ${NVIM_RUNTIME_DIR}/doc/api.mpack diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 4e4befee1d..1f0e867162 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -216,8 +216,8 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return extmark_to_array(extmark, false, details, hl_name); } -/// Gets |extmarks| (including |signs|) in "traversal order" from a |charwise| -/// region defined by buffer positions (inclusive, 0-indexed |api-indexing|). +/// Gets |extmarks| in "traversal order" from a |charwise| region defined by +/// buffer positions (inclusive, 0-indexed |api-indexing|). /// /// Region can be given as (row,col) tuples, or valid extmark ids (whose /// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1) @@ -235,6 +235,10 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// the `overlap` option might be useful. Otherwise only the start position /// of an extmark will be considered. /// +/// Note: legacy signs placed through the |:sign| commands are implemented +/// as extmarks and will show up here. Their details array will contain a +/// `sign_name` field. +/// /// Example: /// /// ```lua @@ -435,7 +439,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// if text around the mark was deleted and then restored by undo. /// Defaults to true. /// - invalidate : boolean that indicates whether to hide the -/// extmark if the entirety of its range is deleted. If +/// extmark if the entirety of its range is deleted. For +/// hidden marks, an "invalid" key is added to the "details" +/// array of |nvim_buf_get_extmarks()| and family. If /// "undo_restore" is false, the extmark is deleted instead. /// - priority: a priority value for the highlight group or sign /// attribute. For example treesitter highlighting uses a diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index bda0c72423..c2f02c34f8 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -39,6 +39,8 @@ void screenshot(String path) FUNC_API_SINCE(7); void option_set(String name, Object value) FUNC_API_SINCE(4); +void chdir(String path) + FUNC_API_SINCE(12); // Stop event is not exported as such, represented by EOF in the msgpack stream. void stop(void) FUNC_API_NOEXPORT; diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 665e61c277..4587415c3b 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -257,6 +257,7 @@ bool arabic_maycombine(int two) } /// Check whether we are dealing with Arabic combining characters. +/// Returns false for negative values. /// Note: these are NOT really composing characters! /// /// @param one First character. diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index d8a63c1d7b..3d31061d4c 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -141,17 +141,18 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a } } - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); - while (cts.cts_vcol <= wcol && *cts.cts_ptr != NUL) { - // Count a tab for what it's worth (if list mode not on) - csize = win_lbr_chartabsize(&cts, &head); - MB_PTR_ADV(cts.cts_ptr); - cts.cts_vcol += csize; + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, pos->lnum, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + col = 0; + while (col <= wcol && *ci.ptr != NUL) { + CharSize cs = win_charsize(cstype, col, ci.ptr, ci.chr.value, &csarg); + csize = cs.width; + head = cs.head; + col += cs.width; + ci = utfc_next(ci); } - col = cts.cts_vcol; - idx = (int)(cts.cts_ptr - line); - clear_chartabsize_arg(&cts); + idx = (int)(ci.ptr - line); // Handle all the special cases. The virtual_active() check // is needed to ensure that a virtual position off the end of diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 7cce6b3ad1..5224a07fd9 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -975,19 +975,19 @@ void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name) if (sh_hl.hl_id) { PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name)); PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol)); - if (sh_hl.flags & kSHConceal) { - char buf[MAX_SCHAR_SIZE]; - schar_get(buf, sh_hl.text[0]); - PUT(*dict, "conceal", CSTR_TO_OBJ(buf)); - } + priority = sh_hl.priority; + } - if (sh_hl.flags & kSHSpellOn) { - PUT(*dict, "spell", BOOLEAN_OBJ(true)); - } else if (sh_hl.flags & kSHSpellOff) { - PUT(*dict, "spell", BOOLEAN_OBJ(false)); - } + if (sh_hl.flags & kSHConceal) { + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, sh_hl.text[0]); + PUT(*dict, "conceal", CSTR_TO_OBJ(buf)); + } - priority = sh_hl.priority; + if (sh_hl.flags & kSHSpellOn) { + PUT(*dict, "spell", BOOLEAN_OBJ(true)); + } else if (sh_hl.flags & kSHSpellOff) { + PUT(*dict, "spell", BOOLEAN_OBJ(false)); } if (sh_hl.flags & kSHUIWatched) { diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c index cead63b88d..7afa2493b9 100644 --- a/src/nvim/drawline.c +++ b/src/nvim/drawline.c @@ -1336,30 +1336,30 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s if (start_col > 0 && col_rows == 0) { char *prev_ptr = ptr; - chartabsize_T cts; - int charsize = 0; - int head = 0; - - init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr); - cts.cts_max_head_vcol = start_col; - while (cts.cts_vcol < start_col && *cts.cts_ptr != NUL) { - head = 0; - charsize = win_lbr_chartabsize(&cts, &head); - cts.cts_vcol += charsize; - prev_ptr = cts.cts_ptr; - MB_PTR_ADV(cts.cts_ptr); + CharSize cs = { 0 }; + + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, wp, lnum, line); + csarg.max_head_vcol = start_col; + int vcol = wlv.vcol; + StrCharInfo ci = utf_ptr2StrCharInfo(ptr); + while (vcol < start_col && *ci.ptr != NUL) { + cs = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg); + vcol += cs.width; + prev_ptr = ci.ptr; + ci = utfc_next(ci); if (wp->w_p_list) { - in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' ' + in_multispace = *prev_ptr == ' ' && (*ci.ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' ')); if (!in_multispace) { multispace_pos = 0; - } else if (cts.cts_ptr >= line + leadcol + } else if (ci.ptr >= line + leadcol && wp->w_p_lcs_chars.multispace != NULL) { multispace_pos++; if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) { multispace_pos = 0; } - } else if (cts.cts_ptr < line + leadcol + } else if (ci.ptr < line + leadcol && wp->w_p_lcs_chars.leadmultispace != NULL) { multispace_pos++; if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) { @@ -1368,9 +1368,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s } } } - wlv.vcol = cts.cts_vcol; - ptr = cts.cts_ptr; - clear_chartabsize_arg(&cts); + wlv.vcol = vcol; + ptr = ci.ptr; + int charsize = cs.width; + int head = cs.head; // When: // - 'cuc' is set, or @@ -2081,13 +2082,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) { int mb_off = utf_head_off(line, ptr - 1); char *p = ptr - (mb_off + 1); - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, p); - // do not want virtual text to be counted here - cts.cts_has_virt_text = false; - wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1; - clear_chartabsize_arg(&cts); + CharsizeArg csarg; + // lnum == 0, do not want virtual text to be counted here + CSType cstype = init_charsize_arg(&csarg, wp, 0, line); + wlv.n_extra = win_charsize(cstype, wlv.vcol, p, utf_ptr2CharInfo(p).value, + &csarg).width - 1; if (on_last_col && mb_c != TAB) { // Do not continue search/match highlighting over the diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 6f05ba5905..1415f0ca35 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1679,33 +1679,37 @@ void change_indent(int type, int amount, int round, int replaced, bool call_chan } else { // Compute the screen column where the cursor should be. vcol = get_indent() - vcol; - curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); + int const end_vcol = (colnr_T)((vcol < 0) ? 0 : vcol); + curwin->w_virtcol = end_vcol; // Advance the cursor until we reach the right screen column. - int last_vcol = 0; - char *ptr = get_cursor_line_ptr(); - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr); - while (cts.cts_vcol <= (int)curwin->w_virtcol) { - last_vcol = cts.cts_vcol; - if (cts.cts_vcol > 0) { - MB_PTR_ADV(cts.cts_ptr); - } - if (*cts.cts_ptr == NUL) { - break; + new_cursor_col = 0; + char *const line = get_cursor_line_ptr(); + vcol = 0; + if (*line != NUL) { + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, 0, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (true) { + int next_vcol = vcol + win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + if (next_vcol > end_vcol) { + break; + } + vcol = next_vcol; + ci = utfc_next(ci); + if (*ci.ptr == NUL) { + break; + } } - cts.cts_vcol += lbr_chartabsize(&cts); + new_cursor_col = (int)(ci.ptr - line); } - vcol = last_vcol; - new_cursor_col = (int)(cts.cts_ptr - cts.cts_line); - clear_chartabsize_arg(&cts); // May need to insert spaces to be able to position the cursor on // the right screen column. if (vcol != (int)curwin->w_virtcol) { curwin->w_cursor.col = (colnr_T)new_cursor_col; size_t i = (size_t)(curwin->w_virtcol - vcol); - ptr = xmallocz(i); + char *ptr = xmallocz(i); memset(ptr, ' ', i); new_cursor_col += (int)i; ins_str(ptr); @@ -4347,14 +4351,16 @@ static bool ins_tab(void) getvcol(curwin, cursor, &want_vcol, NULL, NULL); char *tab = "\t"; - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, vcol, tab, tab); + int32_t tab_v = (uint8_t)(*tab); + + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, 0, tab); // Use as many TABs as possible. Beware of 'breakindent', 'showbreak' // and 'linebreak' adding extra virtual columns. while (ascii_iswhite(*ptr)) { - int i = lbr_chartabsize(&cts); - if (cts.cts_vcol + i > want_vcol) { + int i = win_charsize(cstype, vcol, tab, tab_v, &csarg).width; + if (vcol + i > want_vcol) { break; } if (*ptr != TAB) { @@ -4369,23 +4375,18 @@ static bool ins_tab(void) } fpos.col++; ptr++; - cts.cts_vcol += i; + vcol += i; } - vcol = cts.cts_vcol; - clear_chartabsize_arg(&cts); if (change_col >= 0) { int repl_off = 0; // Skip over the spaces we need. - init_chartabsize_arg(&cts, curwin, 0, vcol, ptr, ptr); - while (cts.cts_vcol < want_vcol && *cts.cts_ptr == ' ') { - cts.cts_vcol += lbr_chartabsize(&cts); - cts.cts_ptr++; + cstype = init_charsize_arg(&csarg, curwin, 0, ptr); + while (vcol < want_vcol && *ptr == ' ') { + vcol += win_charsize(cstype, vcol, ptr, (uint8_t)(' '), &csarg).width; + ptr++; repl_off++; } - ptr = cts.cts_ptr; - vcol = cts.cts_vcol; - clear_chartabsize_arg(&cts); if (vcol > want_vcol) { // Must have a char with 'showbreak' just before it. @@ -4556,8 +4557,6 @@ static int ins_digraph(void) // Returns the char to be inserted, or NUL if none found. int ins_copychar(linenr_T lnum) { - char *ptr; - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { vim_beep(BO_COPY); return NUL; @@ -4565,24 +4564,22 @@ int ins_copychar(linenr_T lnum) // try to advance to the cursor column validate_virtcol(); + int const end_vcol = curwin->w_virtcol; char *line = ml_get(lnum); - char *prev_ptr = line; - - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, lnum, 0, line, line); - while (cts.cts_vcol < curwin->w_virtcol && *cts.cts_ptr != NUL) { - prev_ptr = cts.cts_ptr; - cts.cts_vcol += lbr_chartabsize_adv(&cts); - } - if (cts.cts_vcol > curwin->w_virtcol) { - ptr = prev_ptr; - } else { - ptr = cts.cts_ptr; + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, lnum, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + int vcol = 0; + while (vcol < end_vcol && *ci.ptr != NUL) { + vcol += win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + if (vcol > end_vcol) { + break; + } + ci = utfc_next(ci); } - clear_chartabsize_arg(&cts); - int c = utf_ptr2char(ptr); + int c = ci.chr.value < 0 ? (uint8_t)(*ci.ptr) : ci.chr.value; if (c == NUL) { vim_beep(BO_COPY); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index a820f11a5e..37fded85cd 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -2202,6 +2202,7 @@ M.funcs = { echo exists("*strftime") echo exists("*s:MyFunc") echo exists("*MyFunc") + echo exists("*v:lua.Func") echo exists("bufcount") echo exists(":Make") echo exists("#CursorHold") diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 26e4e24124..d5d16fb1ef 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1726,7 +1726,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) n = false; // Trailing garbage. } } else if (*p == '*') { // Internal or user defined function. - n = function_exists(p + 1, false); + n = strncmp(p, "*v:lua.", 7) == 0 ? nlua_func_exists(p + 7) : function_exists(p + 1, false); } else if (*p == ':') { n = cmd_exists(p + 1); } else if (*p == '#') { @@ -1735,8 +1735,6 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) } else { n = au_exists(p + 1); } - } else if (strncmp(p, "v:lua.", 6) == 0 && nlua_func_exists(p + 6)) { - n = true; } else { // Internal variable. n = var_exists(p); } diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index 0321a11b0f..4e4db93a7b 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -150,7 +150,11 @@ void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore) } if (mt_decor_any(key)) { - buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true); + if (mt_invalid(key)) { + decor_free(mt_decor(key)); + } else { + buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true); + } } // TODO(bfredl): delete it from current undo header, opportunistically? @@ -352,14 +356,12 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln // Push mark to undo header if (only_copy || (uvp != NULL && op == kExtmarkUndo && !mt_no_undo(mark))) { - ExtmarkSavePos pos; - pos.mark = mt_lookup_key(mark); - pos.invalidated = invalidated; - pos.old_row = mark.pos.row; - pos.old_col = mark.pos.col; - pos.row = -1; - pos.col = -1; - + ExtmarkSavePos pos = { + .mark = mt_lookup_key(mark), + .invalidated = invalidated, + .old_row = mark.pos.row, + .old_col = mark.pos.col + }; undo.data.savepos = pos; undo.type = kExtmarkSavePos; kv_push(*uvp, undo); @@ -393,22 +395,17 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) } else if (undo_info.type == kExtmarkSavePos) { ExtmarkSavePos pos = undo_info.data.savepos; if (undo) { - if (pos.old_row >= 0) { - extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col); - } - if (pos.invalidated) { + if (pos.old_row >= 0 + && extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col) + && pos.invalidated) { MarkTreeIter itr[1] = { 0 }; MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr); mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID; MTPos end = marktree_get_altpos(curbuf->b_marktree, mark, itr); buf_put_decor(curbuf, mt_decor(mark), mark.pos.row, end.row); } - // Redo - } else { - if (pos.row >= 0) { - extmark_setraw(curbuf, pos.mark, pos.row, pos.col); - } } + // No Redo since kExtmarkSplice will move marks back } else if (undo_info.type == kExtmarkMove) { ExtmarkMove move = undo_info.data.move; if (undo) { diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index a68bbf33f2..b1ef5cf214 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -45,8 +45,6 @@ typedef struct { uint64_t mark; // raw mark id of the marktree int old_row; colnr_T old_col; - int row; - colnr_T col; bool invalidated; } ExtmarkSavePos; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 23937a6bb5..03bc953368 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -2502,20 +2502,22 @@ static int vgetorpeek(bool advance) // we are expecting to truncate the trailing // white-space, so find the last non-white // character -- webb - if (did_ai - && *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) { + if (did_ai && *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) { curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, ptr, ptr); - while (cts.cts_ptr < ptr + curwin->w_cursor.col) { - if (!ascii_iswhite(*cts.cts_ptr)) { - curwin->w_wcol = cts.cts_vcol; + char *endptr = ptr + curwin->w_cursor.col; + + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, ptr); + StrCharInfo ci = utf_ptr2StrCharInfo(ptr); + int vcol = 0; + while (ci.ptr < endptr) { + if (!ascii_iswhite(ci.chr.value)) { + curwin->w_wcol = vcol; } - cts.cts_vcol += lbr_chartabsize(&cts); - cts.cts_ptr += utfc_ptr2len(cts.cts_ptr); + vcol += win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + ci = utfc_next(ci); } - clear_chartabsize_arg(&cts); curwin->w_wrow = curwin->w_cline_row + curwin->w_wcol / curwin->w_width_inner; diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 9c12a5bda1..e9fd1c3f44 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -230,60 +230,98 @@ static const char *highlight_init_both[] = { "default link DiagnosticSignOk DiagnosticOk", "default link DiagnosticUnnecessary Comment", - // Text - "default link @text.literal Comment", - "default link @text.reference Identifier", - "default link @text.title Title", - "default link @text.uri Underlined", - "default link @text.underline Underlined", - "default link @text.todo Todo", - - // Miscs - "default link @comment Comment", - "default link @punctuation Delimiter", - - // Constants + // Treesitter standard groups + "default link @variable NONE", // don't highlight to reduce visual overload + "default link @variable.builtin Special", + "default link @variable.parameter Identifier", + "default link @variable.member Identifier", + "default link @constant Constant", "default link @constant.builtin Special", "default link @constant.macro Define", - "default link @define Define", - "default link @macro Macro", - "default link @string String", - "default link @string.escape SpecialChar", - "default link @string.special SpecialChar", - "default link @character Character", - "default link @character.special SpecialChar", - "default link @number Number", - "default link @boolean Boolean", - "default link @float Float", - - // Functions - "default link @function Function", - "default link @function.builtin Special", - "default link @function.macro Macro", - "default link @parameter Identifier", - "default link @method Function", - "default link @field Identifier", - "default link @property Identifier", - "default link @constructor Special", - - // Keywords - "default link @conditional Conditional", - "default link @repeat Repeat", - "default link @label Label", - "default link @operator Operator", - "default link @keyword Keyword", - "default link @exception Exception", - - "default link @variable NONE", // don't highlight to reduce visual overload - "default link @type Type", - "default link @type.definition Typedef", - "default link @storageclass StorageClass", - "default link @namespace Identifier", - "default link @include Include", - "default link @preproc PreProc", - "default link @debug Debug", - "default link @tag Tag", + + "default link @module Structure", + "default link @label Label", + + "default link @string String", + "default link @string.regexp SpecialChar", + "default link @string.escape SpecialChar", + "default link @string.special SpecialChar", + "default link @string.special.symbol Constant", + "default link @string.special.url Underlined", + + "default link @character Character", + "default link @character.special SpecialChar", + + "default link @boolean Boolean", + "default link @number Number", + "default link @number.float Float", + + "default link @type Type", + "default link @type.builtin Special", + "default link @type.definition Typedef", + "default link @type.qualifier StorageClass", + + "default link @attribute Macro", + "default link @property Identifier", + + "default link @function Function", + "default link @function.builtin Special", + "default link @function.macro Macro", + + "default link @constructor Special", + "default link @operator Operator", + + "default link @keyword Keyword", + "default link @keyword.function Statement", + "default link @keyword.operator Operator", + "default link @keyword.import Include", + "default link @keyword.storage StorageClass", + "default link @keyword.repeat Repeat", + "default link @keyword.debug Debug", + "default link @keyword.exception Exception", + + "default link @keyword.conditional Conditional", + + "default link @keyword.directive Preproc", + "default link @keyword.directive.define Define", + + "default link @punctuation.delimiter Delimiter", + "default link @punctuation.bracket Delimiter", + "default link @punctuation.special Special", + + "default link @comment Comment", + + "default link @comment.error DiagnosticError", + "default link @comment.warning DiagnosticWarn", + "default link @comment.note DiagnosticInfo", + "default link @comment.todo Todo", + + "@markup.strong gui=bold cterm=bold", + "@markup.italic gui=italic cterm=italic", + "@markup.strikethrough gui=strikethrough, cterm=strikethrough", + "@markup.underline gui=underline, cterm=underline", + + "default link @markup.heading Title", + + "default link @markup.raw Comment", + "default link @markup.quote Comment", + "default link @markup.math Comment", + "default link @markup.environment Comment", + + "default link @markup.link Underlined", + "default link @markup.link.label Identifier", + + "default link @markup.list Special", + "default link @markup.list.checked DiagnosticOk", + "default link @markup.list.unchecked DiagnosticWarn", + + "default link @diff.plus Added", + "default link @diff.minus Removed", + "default link @diff.delta Changed", + + "default link @tag Tag", + "default link @tag.delimiter Delimiter", // LSP semantic tokens "default link @lsp.type.class Structure", diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 080f6f986a..8d97f2ca03 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -1246,18 +1246,19 @@ int get_lisp_indent(void) curwin->w_cursor.col = pos->col; colnr_T col = pos->col; - char *that = get_cursor_line_ptr(); + char *line = get_cursor_line_ptr(); - char *line = that; - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, pos->lnum, 0, line, line); - while (*cts.cts_ptr != NUL && col > 0) { - cts.cts_vcol += lbr_chartabsize_adv(&cts); + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, pos->lnum, line); + + StrCharInfo sci = utf_ptr2StrCharInfo(line); + amount = 0; + while (*sci.ptr != NUL && col > 0) { + amount += win_charsize(cstype, amount, sci.ptr, sci.chr.value, &csarg).width; + sci = utfc_next(sci); col--; } - amount = cts.cts_vcol; - that = cts.cts_ptr; - clear_chartabsize_arg(&cts); + char *that = sci.ptr; // Some keywords require "body" indenting rules (the // non-standard-lisp ones are Scheme special forms): @@ -1272,15 +1273,10 @@ int get_lisp_indent(void) } colnr_T firsttry = amount; - init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line), - amount, line, that); - while (ascii_iswhite(*cts.cts_ptr)) { - cts.cts_vcol += lbr_chartabsize(&cts); - cts.cts_ptr++; + while (ascii_iswhite(*that)) { + amount += win_charsize(cstype, amount, that, (uint8_t)(*that), &csarg).width; + that++; } - that = cts.cts_ptr; - amount = cts.cts_vcol; - clear_chartabsize_arg(&cts); if (*that && (*that != ';')) { // Not a comment line. @@ -1292,37 +1288,38 @@ int get_lisp_indent(void) parencount = 0; - init_chartabsize_arg(&cts, curwin, - (colnr_T)(that - line), amount, line, that); - if (((*that != '"') && (*that != '\'') && (*that != '#') - && (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) { + CharInfo ci = utf_ptr2CharInfo(that); + if (((ci.value != '"') && (ci.value != '\'') && (ci.value != '#') + && ((ci.value < '0') || (ci.value > '9')))) { int quotecount = 0; - while (*cts.cts_ptr - && (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount)) { - if (*cts.cts_ptr == '"') { + while (*that && (!ascii_iswhite(ci.value) || quotecount || parencount)) { + if (ci.value == '"') { quotecount = !quotecount; } - if (((*cts.cts_ptr == '(') || (*cts.cts_ptr == '[')) && !quotecount) { + if (((ci.value == '(') || (ci.value == '[')) && !quotecount) { parencount++; } - if (((*cts.cts_ptr == ')') || (*cts.cts_ptr == ']')) && !quotecount) { + if (((ci.value == ')') || (ci.value == ']')) && !quotecount) { parencount--; } - if ((*cts.cts_ptr == '\\') && (*(cts.cts_ptr + 1) != NUL)) { - cts.cts_vcol += lbr_chartabsize_adv(&cts); + if ((ci.value == '\\') && (*(that + 1) != NUL)) { + amount += win_charsize(cstype, amount, that, ci.value, &csarg).width; + StrCharInfo next_sci = utfc_next((StrCharInfo){ that, ci }); + that = next_sci.ptr; + ci = next_sci.chr; } - cts.cts_vcol += lbr_chartabsize_adv(&cts); + amount += win_charsize(cstype, amount, that, ci.value, &csarg).width; + StrCharInfo next_sci = utfc_next((StrCharInfo){ that, ci }); + that = next_sci.ptr; + ci = next_sci.chr; } } - while (ascii_iswhite(*cts.cts_ptr)) { - cts.cts_vcol += lbr_chartabsize(&cts); - cts.cts_ptr++; + while (ascii_iswhite(*that)) { + amount += win_charsize(cstype, amount, that, (uint8_t)(*that), &csarg).width; + that++; } - that = cts.cts_ptr; - amount = cts.cts_vcol; - clear_chartabsize_arg(&cts); if (!*that || (*that == ';')) { amount = firsttry; diff --git a/src/nvim/macros_defs.h b/src/nvim/macros_defs.h index a0dcafab95..67da29031c 100644 --- a/src/nvim/macros_defs.h +++ b/src/nvim/macros_defs.h @@ -111,10 +111,13 @@ #endif #if defined(__clang__) || defined(__GNUC__) +# define EXPECT(cond, value) __builtin_expect((cond), (value)) # define UNREACHABLE __builtin_unreachable() -#elif defined(_MSVC_VER) +#elif defined(_MSC_VER) +# define EXPECT(cond, value) (cond) # define UNREACHABLE __assume(false) #else +# define EXPECT(cond, value) (cond) # define UNREACHABLE #endif diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c index a8af2168c5..d36fbb32d7 100644 --- a/src/nvim/mapping.c +++ b/src/nvim/mapping.c @@ -36,6 +36,7 @@ #include "nvim/lua/executor.h" #include "nvim/macros_defs.h" #include "nvim/mapping.h" +#include "nvim/mapping_defs.h" #include "nvim/mbyte.h" #include "nvim/mbyte_defs.h" #include "nvim/memory.h" @@ -142,20 +143,6 @@ mapblock_T *get_buf_maphash_list(int state, int c) return curbuf->b_maphash[MAP_HASH(state, c)]; } -/// Retrieve the mapblock at the index either globally or for a certain buffer -/// -/// @param index The index in the maphash[] -/// @param buf The buffer to get the maphash from. NULL for global -mapblock_T *get_maphash(int index, buf_T *buf) - FUNC_ATTR_PURE -{ - if (index >= MAX_MAPHASH) { - return NULL; - } - - return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; -} - /// Delete one entry from the abbrlist or maphash[]. /// "mpp" is a pointer to the m_next field of the PREVIOUS entry! static void mapblock_free(mapblock_T **mpp) @@ -2820,16 +2807,23 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) { Array mappings = ARRAY_DICT_INIT; - // Convert the string mode to the integer mode - // that is stored within each mapblock - char *p = mode.data; - int int_mode = get_map_mode(&p, 0); + char *p = mode.size > 0 ? mode.data : "m"; + bool forceit = *p == '!'; + // Convert the string mode to the integer mode stored within each mapblock. + int int_mode = get_map_mode(&p, forceit); + if (forceit) { + assert(p == mode.data); + p++; + } + bool is_abbrev = (int_mode & (MODE_INSERT | MODE_CMDLINE)) != 0 && *p == 'a'; // Determine the desired buffer value int buffer_value = (buf == NULL) ? 0 : buf->handle; - for (int i = 0; i < MAX_MAPHASH; i++) { - for (const mapblock_T *current_maphash = get_maphash(i, buf); + for (int i = 0; i < (is_abbrev ? 1 : MAX_MAPHASH); i++) { + for (const mapblock_T *current_maphash = is_abbrev + ? (buf ? buf->b_first_abbr : first_abbr) + : (buf ? buf->b_maphash[i] : maphash[i]); current_maphash; current_maphash = current_maphash->m_next) { if (current_maphash->m_simplified) { @@ -2839,7 +2833,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) if (int_mode & current_maphash->m_mode) { ADD(mappings, DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, - buffer_value, false, false))); + buffer_value, is_abbrev, false))); } } } diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 26c95c698f..b1f7b59b21 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -528,6 +528,74 @@ int utf_ptr2cells(const char *p) return 1; } +/// Convert a UTF-8 byte sequence to a character number. +/// Doesn't handle ascii! only multibyte and illegal sequences. +/// +/// @param[in] p String to convert. +/// @param[in] len Length of the character in bytes, 0 or 1 if illegal. +/// +/// @return Unicode codepoint. A negative value When the sequence is illegal. +int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len) + FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +// uint8_t is a reminder for clang to use smaller cmp +#define CHECK \ + do { \ + if (EXPECT((uint8_t)(cur & 0xC0U) != 0x80U, false)) { \ + return -1; \ + } \ + } while (0) + + static uint32_t const corrections[] = { + (1U << 31), // invalid - set invalid bits (safe to add as first 2 bytes + (1U << 31), // won't affect highest bit in normal ret) + -(0x80U + (0xC0U << 6)), // multibyte - subtract added UTF8 bits (1..10xxx and 10xxx) + -(0x80U + (0x80U << 6) + (0xE0U << 12)), + -(0x80U + (0x80U << 6) + (0x80U << 12) + (0xF0U << 18)), + -(0x80U + (0x80U << 6) + (0x80U << 12) + (0x80U << 18) + (0xF8U << 24)), + -(0x80U + (0x80U << 6) + (0x80U << 12) + (0x80U << 18) + (0x80U << 24)), // + (0xFCU << 30) + }; + + // len is 0-6, but declared uintptr_t to avoid zeroing out upper bits + uint32_t const corr = corrections[len]; + uint8_t cur; + + // reading second byte unconditionally, safe for invalid + // as it cannot be the last byte, not safe for ascii + uint32_t code_point = ((uint32_t)p[0] << 6) + (cur = p[1]); + CHECK; + if ((uint32_t)len < 3) { + goto ret; // len == 0, 1, 2 + } + + code_point = (code_point << 6) + (cur = p[2]); + CHECK; + if ((uint32_t)len == 3) { + goto ret; + } + + code_point = (code_point << 6) + (cur = p[3]); + CHECK; + if ((uint32_t)len == 4) { + goto ret; + } + + code_point = (code_point << 6) + (cur = p[4]); + CHECK; + if ((uint32_t)len == 5) { + goto ret; + } + + code_point = (code_point << 6) + (cur = p[5]); + CHECK; + // len == 6 + +ret: + return (int32_t)(code_point + corr); + +#undef CHECK +} + /// Like utf_ptr2cells(), but limit string length to "size". /// For an empty string or truncated character returns 1. int utf_ptr2cells_len(const char *p, int size) @@ -597,45 +665,62 @@ size_t mb_string2cells_len(const char *str, size_t size) /// /// @return Unicode codepoint or byte value. int utf_ptr2char(const char *const p_in) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { uint8_t *p = (uint8_t *)p_in; - if (p[0] < 0x80) { // Be quick for ASCII. - return p[0]; + + uint32_t const v0 = p[0]; + if (EXPECT(v0 < 0x80U, true)) { // Be quick for ASCII. + return (int)v0; } - const uint8_t len = utf8len_tab_zero[p[0]]; - if (len > 1 && (p[1] & 0xc0) == 0x80) { - if (len == 2) { - return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); - } - if ((p[2] & 0xc0) == 0x80) { - if (len == 3) { - return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f)); - } - if ((p[3] & 0xc0) == 0x80) { - if (len == 4) { - return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); - } - if ((p[4] & 0xc0) == 0x80) { - if (len == 5) { - return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) - + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) - + (p[4] & 0x3f)); - } - if ((p[5] & 0xc0) == 0x80 && len == 6) { - return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) - + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) - + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); - } - } - } - } + const uint8_t len = utf8len_tab[v0]; + if (EXPECT(len < 2, false)) { + return (int)v0; } - // Illegal value: just return the first byte. - return p[0]; + +#define CHECK(v) \ + do { \ + if (EXPECT((uint8_t)((v) & 0xC0U) != 0x80U, false)) { \ + return (int)v0; \ + } \ + } while (0) +#define LEN_RETURN(len_v, result) \ + do { \ + if (len == (len_v)) { \ + return (int)(result); \ + } \ + } while (0) +#define S(s) ((uint32_t)0x80U << (s)) + + uint32_t const v1 = p[1]; + CHECK(v1); + LEN_RETURN(2, (v0 << 6) + v1 - ((0xC0U << 6) + S(0))); + + uint32_t const v2 = p[2]; + CHECK(v2); + LEN_RETURN(3, (v0 << 12) + (v1 << 6) + v2 - ((0xE0U << 12) + S(6) + S(0))); + + uint32_t const v3 = p[3]; + CHECK(v3); + LEN_RETURN(4, (v0 << 18) + (v1 << 12) + (v2 << 6) + v3 + - ((0xF0U << 18) + S(12) + S(6) + S(0))); + + uint32_t const v4 = p[4]; + CHECK(v4); + LEN_RETURN(5, (v0 << 24) + (v1 << 18) + (v2 << 12) + (v3 << 6) + v4 + - ((0xF8U << 24) + S(18) + S(12) + S(6) + S(0))); + + uint32_t const v5 = p[5]; + CHECK(v5); + // len == 6 + return (int)((v0 << 30) + (v1 << 24) + (v2 << 18) + (v3 << 12) + (v4 << 6) + v5 + // - (0xFCU << 30) + - (S(24) + S(18) + S(12) + S(6) + S(0))); + +#undef S +#undef CHECK +#undef LEN_RETURN } // Convert a UTF-8 byte sequence to a wide character. @@ -722,6 +807,16 @@ bool utf_composinglike(const char *p1, const char *p2) return arabic_combine(utf_ptr2char(p1), c2); } +/// Check if the next character is a composing character when it +/// comes after the first. For Arabic sometimes "ab" is replaced with "c", which +/// behaves like a composing character. +/// returns false for negative values +bool utf_char_composinglike(int32_t const first, int32_t const next) + FUNC_ATTR_PURE +{ + return utf_iscomposing(next) || arabic_combine(first, next); +} + /// Get the screen char at the beginning of a string /// /// Caller is expected to check for things like unprintable chars etc @@ -988,9 +1083,10 @@ int utf_char2bytes(const int c, char *const buf) } } -// Return true if "c" is a composing UTF-8 character. This means it will be -// drawn on top of the preceding character. -// Based on code from Markus Kuhn. +/// Return true if "c" is a composing UTF-8 character. +/// This means it will be drawn on top of the preceding character. +/// Based on code from Markus Kuhn. +/// Returns false for negative values. bool utf_iscomposing(int c) { return intable(combining, ARRAY_SIZE(combining), c); diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index 2fb353de9e..be632ee834 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -6,6 +6,7 @@ #include "nvim/cmdexpand_defs.h" // IWYU pragma: keep #include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/macros_defs.h" #include "nvim/mbyte_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" // IWYU pragma: keep @@ -13,6 +14,10 @@ # include "mbyte.h.generated.h" #endif +enum { + kInvalidByteCells = 4, +}; + // Return byte length of character that starts with byte "b". // Returns 1 for a single-byte character. // MB_BYTE2LEN_CHECK() can be used to count a special key as one byte. @@ -44,3 +49,72 @@ extern const uint8_t utf8len_tab[256]; // multi-byte characters if needed. Only use with "p" > "s" ! #define MB_PTR_BACK(s, p) \ (p -= utf_head_off((char *)(s), (char *)(p) - 1) + 1) + +static inline CharInfo utf_ptr2CharInfo(char const *p_in) + REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; + +/// Convert a UTF-8 byte sequence to a Unicode code point. +/// Handles ascii, multibyte sequiences and illegal sequences. +/// +/// @param[in] p_in String to convert. +/// +/// @return information abouth the character. When the sequence is illegal, +/// 'value' is negative, 'len' is 1. +static inline CharInfo utf_ptr2CharInfo(char const *const p_in) +{ + uint8_t const *const p = (uint8_t const *)p_in; + uint8_t const first = *p; + if (first < 0x80) { + return (CharInfo){ .value = first, .len = 1 }; + } else { + int len = utf8len_tab[first]; + int32_t const code_point = utf_ptr2CharInfo_impl(p, (uintptr_t)len); + if (code_point < 0) { + len = 1; + } + return (CharInfo){ .value = code_point, .len = len }; + } +} + +static inline StrCharInfo utfc_next(StrCharInfo cur) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE; + +/// Return information about the next character. +/// Composing and combining characters are +/// considered a part of the current character. +/// +/// @param[in] cur Pointer to the current character. Must not point to NUL +/// @param[in] cur_char Decoded charater at 'cur'. +static inline StrCharInfo utfc_next(StrCharInfo cur) +{ + int32_t prev_code = cur.chr.value; + uint8_t *next = (uint8_t *)(cur.ptr + cur.chr.len); + + while (true) { + if (EXPECT(*next < 0x80U, true)) { + return (StrCharInfo){ + .ptr = (char *)next, + .chr = (CharInfo){ .value = *next, .len = 1 }, + }; + } + uint8_t const next_len = utf8len_tab[*next]; + int32_t const next_code = utf_ptr2CharInfo_impl(next, (uintptr_t)next_len); + if (!utf_char_composinglike(prev_code, next_code)) { + return (StrCharInfo){ + .ptr = (char *)next, + .chr = (CharInfo){ .value = next_code, .len = (next_code < 0 ? 1 : next_len) }, + }; + } + + prev_code = next_code; + next += next_len; + } +} + +static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE; + +static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr) +{ + return (StrCharInfo){ .ptr = ptr, .chr = utf_ptr2CharInfo(ptr) }; +} diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h index efb4f558a6..97aa1a638b 100644 --- a/src/nvim/mbyte_defs.h +++ b/src/nvim/mbyte_defs.h @@ -1,6 +1,7 @@ #pragma once #include <stdbool.h> +#include <stdint.h> #include "nvim/iconv_defs.h" @@ -55,3 +56,13 @@ typedef struct { bool vc_fail; ///< What to do with invalid characters: if true, fail, ///< otherwise use '?'. } vimconv_T; + +typedef struct { + int32_t value; ///< code point + int len; ///< length in bytes +} CharInfo; + +typedef struct { + char *ptr; ///< pointer to the first byte of the character + CharInfo chr; ///< the character +} StrCharInfo; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index a6da7dd3c7..fe59ceb82a 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -1755,22 +1755,23 @@ colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp) { // try to advance to the specified column char *line = ml_get_buf(wp->w_buffer, lnum); - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, line, line); - while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) { - int size = win_lbr_chartabsize(&cts, NULL); - if (cts.cts_vcol + size > vcol) { + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, wp, lnum, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + int cur_vcol = 0; + while (cur_vcol < vcol && *ci.ptr != NUL) { + int next_vcol = cur_vcol + win_charsize(cstype, cur_vcol, ci.ptr, ci.chr.value, &csarg).width; + if (next_vcol > vcol) { break; } - cts.cts_vcol += size; - MB_PTR_ADV(cts.cts_ptr); + cur_vcol = next_vcol; + ci = utfc_next(ci); } - clear_chartabsize_arg(&cts); if (coladdp != NULL) { - *coladdp = vcol - cts.cts_vcol; + *coladdp = vcol - cur_vcol; } - return (colnr_T)(cts.cts_ptr - line); + return (colnr_T)(ci.ptr - line); } /// Set UI mouse depending on current mode and 'mouse'. diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 286bb8098a..0178ef622b 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -304,8 +304,11 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, p->read_ptr = rbuffer_read_ptr(rbuf, &size); p->read_size = size; parse_msgpack(channel); - size_t consumed = size - p->read_size; - rbuffer_consumed_compact(rbuf, consumed); + + if (!unpacker_closed(p)) { + size_t consumed = size - p->read_size; + rbuffer_consumed_compact(rbuf, consumed); + } end: channel_decref(channel); diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2819d98208..7f5be88a88 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -387,17 +387,18 @@ static void shift_block(oparg_T *oap, int amount) } // TODO(vim): is passing bd.textstart for start of the line OK? - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, - bd.start_vcol, bd.textstart, bd.textstart); - while (ascii_iswhite(*cts.cts_ptr)) { - incr = lbr_chartabsize_adv(&cts); + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, bd.textstart); + StrCharInfo ci = utf_ptr2StrCharInfo(bd.textstart); + int vcol = bd.start_vcol; + while (ascii_iswhite(ci.chr.value)) { + incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + ci = utfc_next(ci); total += incr; - cts.cts_vcol += incr; + vcol += incr; } - bd.textstart = cts.cts_ptr; - bd.start_vcol = cts.cts_vcol; - clear_chartabsize_arg(&cts); + bd.textstart = ci.ptr; + bd.start_vcol = vcol; int tabs = 0; int spaces = 0; @@ -448,16 +449,13 @@ static void shift_block(oparg_T *oap, int amount) // The character's column is in "bd.start_vcol". colnr_T non_white_col = bd.start_vcol; - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, - non_white_col, bd.textstart, non_white); - while (ascii_iswhite(*cts.cts_ptr)) { - incr = lbr_chartabsize_adv(&cts); - cts.cts_vcol += incr; + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, bd.textstart); + while (ascii_iswhite(*non_white)) { + incr = win_charsize(cstype, non_white_col, non_white, (uint8_t)(*non_white), &csarg).width; + non_white_col += incr; + non_white++; } - non_white_col = cts.cts_vcol; - non_white = cts.cts_ptr; - clear_chartabsize_arg(&cts); const colnr_T block_space_width = non_white_col - oap->start_vcol; // We will shift by "total" or "block_space_width", whichever is less. @@ -478,19 +476,17 @@ static void shift_block(oparg_T *oap, int amount) if (bd.startspaces) { verbatim_copy_width -= bd.start_char_vcols; } - init_chartabsize_arg(&cts, curwin, 0, verbatim_copy_width, - bd.textstart, verbatim_copy_end); - while (cts.cts_vcol < destination_col) { - incr = lbr_chartabsize(&cts); - if (cts.cts_vcol + incr > destination_col) { + cstype = init_charsize_arg(&csarg, curwin, 0, bd.textstart); + StrCharInfo ci = utf_ptr2StrCharInfo(verbatim_copy_end); + while (verbatim_copy_width < destination_col) { + incr = win_charsize(cstype, verbatim_copy_width, ci.ptr, ci.chr.value, &csarg).width; + if (verbatim_copy_width + incr > destination_col) { break; } - cts.cts_vcol += incr; - MB_PTR_ADV(cts.cts_ptr); + verbatim_copy_width += incr; + ci = utfc_next(ci); } - verbatim_copy_width = cts.cts_vcol; - verbatim_copy_end = cts.cts_ptr; - clear_chartabsize_arg(&cts); + verbatim_copy_end = ci.ptr; // If "destination_col" is different from the width of the initial // part of the line that will be copied, it means we encountered a tab @@ -3250,19 +3246,19 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) } // get the old line and advance to the position to insert at char *oldp = get_cursor_line_ptr(); - size_t oldlen = strlen(oldp); - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, oldp, oldp); - while (cts.cts_vcol < col && *cts.cts_ptr != NUL) { - // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize_adv(&cts); - cts.cts_vcol += incr; + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, curwin->w_cursor.lnum, oldp); + StrCharInfo ci = utf_ptr2StrCharInfo(oldp); + vcol = 0; + while (vcol < col && *ci.ptr != NUL) { + incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + vcol += incr; + ci = utfc_next(ci); } - vcol = cts.cts_vcol; - char *ptr = cts.cts_ptr; + size_t oldlen = (size_t)(ci.ptr - oldp) + strlen(ci.ptr); + char *ptr = ci.ptr; bd.textcol = (colnr_T)(ptr - oldp); - clear_chartabsize_arg(&cts); shortline = (vcol < col) || (vcol == col && !*ptr); @@ -3286,16 +3282,15 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags) yanklen = (int)strlen(y_array[i]); if ((flags & PUT_BLOCK_INNER) == 0) { - // calculate number of spaces required to fill right side of - // block + // calculate number of spaces required to fill right side of block spaces = y_width + 1; - init_chartabsize_arg(&cts, curwin, 0, 0, y_array[i], y_array[i]); - for (int j = 0; j < yanklen; j++) { - spaces -= lbr_chartabsize(&cts); - cts.cts_ptr++; - cts.cts_vcol = 0; + + cstype = init_charsize_arg(&csarg, curwin, 0, y_array[i]); + ci = utf_ptr2StrCharInfo(y_array[i]); + while (*ci.ptr != NUL) { + spaces -= win_charsize(cstype, 0, ci.ptr, ci.chr.value, &csarg).width; + ci = utfc_next(ci); } - clear_chartabsize_arg(&cts); if (spaces < 0) { spaces = 0; } @@ -4228,25 +4223,25 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool char *line = ml_get(lnum); char *prev_pstart = line; - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line); - while (cts.cts_vcol < oap->start_vcol && *cts.cts_ptr != NUL) { - // Count a tab for what it's worth (if list mode not on) - incr = lbr_chartabsize(&cts); - cts.cts_vcol += incr; - if (ascii_iswhite(*cts.cts_ptr)) { + CharsizeArg csarg; + CSType cstype = init_charsize_arg(&csarg, curwin, lnum, line); + StrCharInfo ci = utf_ptr2StrCharInfo(line); + int vcol = bdp->start_vcol; + while (vcol < oap->start_vcol && *ci.ptr != NUL) { + incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + vcol += incr; + if (ascii_iswhite(ci.chr.value)) { bdp->pre_whitesp += incr; bdp->pre_whitesp_c++; } else { bdp->pre_whitesp = 0; bdp->pre_whitesp_c = 0; } - prev_pstart = cts.cts_ptr; - MB_PTR_ADV(cts.cts_ptr); + prev_pstart = ci.ptr; + ci = utfc_next(ci); } - bdp->start_vcol = cts.cts_vcol; - char *pstart = cts.cts_ptr; - clear_chartabsize_arg(&cts); + bdp->start_vcol = vcol; + char *pstart = ci.ptr; bdp->start_char_vcols = incr; if (bdp->start_vcol < oap->start_vcol) { // line too short @@ -4283,17 +4278,18 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool } } } else { - init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, line, pend); + cstype = init_charsize_arg(&csarg, curwin, lnum, line); + ci = utf_ptr2StrCharInfo(pend); + vcol = bdp->end_vcol; char *prev_pend = pend; - while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) { - // Count a tab for what it's worth (if list mode not on) - prev_pend = cts.cts_ptr; - incr = lbr_chartabsize_adv(&cts); - cts.cts_vcol += incr; - } - bdp->end_vcol = cts.cts_vcol; - pend = cts.cts_ptr; - clear_chartabsize_arg(&cts); + while (vcol <= oap->end_vcol && *ci.ptr != NUL) { + prev_pend = ci.ptr; + incr = win_charsize(cstype, vcol, ci.ptr, ci.chr.value, &csarg).width; + vcol += incr; + ci = utfc_next(ci); + } + bdp->end_vcol = vcol; + pend = ci.ptr; if (bdp->end_vcol <= oap->end_vcol && (!is_del diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 09203990bb..d80539708d 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -33,6 +33,7 @@ # include <sys/xattr.h> #endif +#include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" #include "nvim/gettext_defs.h" #include "nvim/globals.h" @@ -44,6 +45,7 @@ #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/types_defs.h" +#include "nvim/ui.h" #include "nvim/vim_defs.h" #ifdef HAVE_SYS_UIO_H @@ -90,7 +92,11 @@ int os_chdir(const char *path) smsg(0, "chdir(%s)", path); verbose_leave(); } - return uv_chdir(path); + int err = uv_chdir(path); + if (err == 0) { + ui_call_chdir(cstr_as_string((char *)path)); + } + return err; } /// Get the name of current directory. diff --git a/src/nvim/plines.c b/src/nvim/plines.c index c2cf3796a7..c59c23179e 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -51,48 +51,21 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col) return ptr2cells(p); } -/// Return the number of characters the string 's' will take on the screen, -/// taking into account the size of a tab. -/// -/// @param s -/// -/// @return Number of characters the string will take on the screen. -int linetabsize_str(char *s) -{ - return linetabsize_col(0, s); -} - -/// Like linetabsize_str(), but "s" starts at column "startcol". +/// Like linetabsize_str(), but "s" starts at virtual column "startvcol". /// /// @param startcol /// @param s /// /// @return Number of characters the string will take on the screen. -int linetabsize_col(int startcol, char *s) +int linetabsize_col(int startvcol, char *s) { - chartabsize_T cts; - init_chartabsize_arg(&cts, curwin, 0, startcol, s, s); - while (*cts.cts_ptr != NUL) { - cts.cts_vcol += lbr_chartabsize_adv(&cts); + CharsizeArg csarg; + CSType const cstype = init_charsize_arg(&csarg, curwin, 0, s); + if (cstype == kCharsizeFast) { + return linesize_fast(&csarg, startvcol, MAXCOL); + } else { + return linesize_regular(&csarg, startvcol, MAXCOL); } - clear_chartabsize_arg(&cts); - return cts.cts_vcol; -} - -/// Like linetabsize_str(), but for a given window instead of the current one. -/// -/// @param wp -/// @param line -/// @param len -/// -/// @return Number of characters the string will take on the screen. -int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) -{ - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, line, line); - win_linetabsize_cts(&cts, len); - clear_chartabsize_arg(&cts); - return cts.cts_vcol; } /// Return the number of cells line "lnum" of window "wp" will take on the @@ -102,127 +75,80 @@ int linetabsize(win_T *wp, linenr_T lnum) return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL); } -void win_linetabsize_cts(chartabsize_T *cts, colnr_T len) -{ - for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len); - MB_PTR_ADV(cts->cts_ptr)) { - cts->cts_vcol += win_lbr_chartabsize(cts, NULL); - } - // check for inline virtual text after the end of the line - if (len == MAXCOL && cts->cts_has_virt_text && *cts->cts_ptr == NUL) { - win_lbr_chartabsize(cts, NULL); - cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right; - } -} - -/// Prepare the structure passed to chartabsize functions. +/// Prepare the structure passed to charsize functions. /// -/// "line" is the start of the line, "ptr" is the first relevant character. +/// "line" is the start of the line. /// When "lnum" is zero do not use inline virtual text. -void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line, - char *ptr) +CSType init_charsize_arg(CharsizeArg *csarg, win_T *wp, linenr_T lnum, char *line) { - cts->cts_win = wp; - cts->cts_vcol = col; - cts->cts_line = line; - cts->cts_ptr = ptr; - cts->cts_max_head_vcol = 0; - cts->cts_cur_text_width_left = 0; - cts->cts_cur_text_width_right = 0; - cts->cts_has_virt_text = false; - cts->cts_row = lnum - 1; - - if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) { - marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter); - MTKey mark = marktree_itr_current(cts->cts_iter); - if (mark.pos.row == cts->cts_row) { - cts->cts_has_virt_text = true; + csarg->win = wp; + csarg->line = line; + csarg->max_head_vcol = 0; + csarg->cur_text_width_left = 0; + csarg->cur_text_width_right = 0; + csarg->virt_row = -1; + csarg->indent_width = INT_MIN; + csarg->use_tabstop = !wp->w_p_list || wp->w_p_lcs_chars.tab1; + + if (lnum > 0 && wp->w_buffer->b_virt_text_inline > 0) { + marktree_itr_get(wp->w_buffer->b_marktree, lnum - 1, 0, csarg->iter); + MTKey mark = marktree_itr_current(csarg->iter); + if (mark.pos.row == lnum - 1) { + csarg->virt_row = lnum - 1; } } -} - -/// Free any allocated item in "cts". -void clear_chartabsize_arg(chartabsize_T *cts) -{ -} -/// like win_chartabsize(), but also check for line breaks on the screen -/// -/// @param cts -/// -/// @return The number of characters taken up on the screen. -int lbr_chartabsize(chartabsize_T *cts) -{ - if (!curwin->w_p_lbr && *get_showbreak_value(curwin) == NUL - && !curwin->w_p_bri && !cts->cts_has_virt_text) { - if (curwin->w_p_wrap) { - return win_nolbr_chartabsize(cts, NULL); - } - return win_chartabsize(curwin, cts->cts_ptr, cts->cts_vcol); + if (csarg->virt_row >= 0 + || (wp->w_p_wrap && (wp->w_p_lbr || wp->w_p_bri || *get_showbreak_value(wp) != NUL))) { + return kCharsizeRegular; + } else { + return kCharsizeFast; } - return win_lbr_chartabsize(cts, NULL); } -/// Call lbr_chartabsize() and advance the pointer. -/// -/// @param cts -/// -/// @return The number of characters take up on the screen. -int lbr_chartabsize_adv(chartabsize_T *cts) -{ - int retval = lbr_chartabsize(cts); - MB_PTR_ADV(cts->cts_ptr); - return retval; -} - -/// Get the number of characters taken up on the screen indicated by "cts". -/// "cts->cts_cur_text_width_left" and "cts->cts_cur_text_width_right" are set +/// Get the number of characters taken up on the screen for the given cts and position. +/// "cts->cur_text_width_left" and "cts->cur_text_width_right" are set /// to the extra size for inline virtual text. /// This function is used very often, keep it fast!!!! /// -/// If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent' -/// included in the return value. -/// When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size -/// of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol". -/// When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size +/// When "cts->max_head_vcol" is positive, only count in "head" the size +/// of 'showbreak'/'breakindent' before "cts->max_head_vcol". +/// When "cts->max_head_vcol" is negative, only count in "head" the size /// of 'showbreak'/'breakindent' before where cursor should be placed. -/// -/// Warning: "*headp" may not be set if it's 0, init to 0 before calling. -int win_lbr_chartabsize(chartabsize_T *cts, int *headp) +CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vcol, + int32_t const cur_char) { - win_T *wp = cts->cts_win; - char *line = cts->cts_line; // start of the line - char *s = cts->cts_ptr; - colnr_T vcol = cts->cts_vcol; - int mb_added = 0; + csarg->cur_text_width_left = 0; + csarg->cur_text_width_right = 0; - cts->cts_cur_text_width_left = 0; - cts->cts_cur_text_width_right = 0; - - // No 'linebreak', 'showbreak' and 'breakindent': return quickly. - if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL - && !cts->cts_has_virt_text) { - if (wp->w_p_wrap) { - return win_nolbr_chartabsize(cts, headp); - } - return win_chartabsize(wp, s, vcol); - } + win_T *wp = csarg->win; + buf_T *buf = wp->w_buffer; + char *line = csarg->line; + bool const use_tabstop = cur_char == TAB && csarg->use_tabstop; + int mb_added = 0; bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL; // First get normal size, without 'linebreak' or inline virtual text - int size = win_chartabsize(wp, s, vcol); - if (*s == NUL && !has_lcs_eol) { - size = 0; // NUL is not displayed + int size; + int is_doublewidth = false; + if (use_tabstop) { + size = tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array); + } else if (*cur == NUL && !has_lcs_eol) { + size = 0; + } else if (cur_char < 0) { + size = kInvalidByteCells; + } else { + size = char2cells(cur_char); + is_doublewidth = size == 2 && cur_char > 0x80; } - bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1; - if (cts->cts_has_virt_text) { + if (csarg->virt_row >= 0) { int tab_size = size; - int col = (int)(s - line); + int col = (int)(cur - line); while (true) { - MTKey mark = marktree_itr_current(cts->cts_iter); - if (mark.pos.row != cts->cts_row || mark.pos.col > col) { + MTKey mark = marktree_itr_current(csarg->iter); + if (mark.pos.row != csarg->virt_row || mark.pos.col > col) { break; } else if (mark.pos.col == col) { if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) { @@ -231,15 +157,15 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) while (vt) { if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) { if (mt_right(mark)) { - cts->cts_cur_text_width_right += vt->width; + csarg->cur_text_width_right += vt->width; } else { - cts->cts_cur_text_width_left += vt->width; + csarg->cur_text_width_left += vt->width; } size += vt->width; - if (*s == TAB) { + if (use_tabstop) { // tab size changes because of the inserted text size -= tab_size; - tab_size = win_chartabsize(wp, s, vcol + size); + tab_size = tabstop_padding(vcol + size, buf->b_p_ts, buf->b_p_vts_array); size += tab_size; } } @@ -247,7 +173,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } } } - marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter); + marktree_itr_next(wp->w_buffer->b_marktree, csarg->iter); } } @@ -257,16 +183,17 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) mb_added = 1; } + char *const sbr = get_showbreak_value(wp); + // May have to add something for 'breakindent' and/or 'showbreak' // string at the start of a screen line. int head = mb_added; - char *const sbr = get_showbreak_value(wp); // When "size" is 0, no new screen line is started. if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) { int col_off_prev = win_col_off(wp); int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp); colnr_T wcol = vcol + col_off_prev; - colnr_T max_head_vcol = cts->cts_max_head_vcol; + colnr_T max_head_vcol = csarg->max_head_vcol; int added = 0; // cells taken by 'showbreak'/'breakindent' before current char @@ -277,11 +204,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (wcol >= width2 && width2 > 0) { wcol %= width2; } - if (*sbr != NUL) { - head_prev += vim_strsize(sbr); - } - if (wp->w_p_bri) { - head_prev += get_breakindent_win(wp, line); + head_prev = csarg->indent_width; + if (head_prev == INT_MIN) { + head_prev = 0; + if (*sbr != NUL) { + head_prev += vim_strsize(sbr); + } + if (wp->w_p_bri) { + head_prev += get_breakindent_win(wp, line); + } + csarg->indent_width = head_prev; } if (wcol < head_prev) { head_prev -= wcol; @@ -298,12 +230,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) if (wcol + size > wp->w_width) { // cells taken by 'showbreak'/'breakindent' halfway current char - int head_mid = 0; - if (*sbr != NUL) { - head_mid += vim_strsize(sbr); - } - if (wp->w_p_bri) { - head_mid += get_breakindent_win(wp, line); + int head_mid = csarg->indent_width; + if (head_mid == INT_MIN) { + head_mid = 0; + if (*sbr != NUL) { + head_mid += vim_strsize(sbr); + } + if (wp->w_p_bri) { + head_mid += get_breakindent_win(wp, line); + } + csarg->indent_width = head_mid; } if (head_mid > 0 && wcol + size > wp->w_width_inner) { // Calculate effective window width. @@ -323,7 +259,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) head += (max_head_vcol - (vcol + head_prev + prev_rem) + width2 - 1) / width2 * head_mid; } else if (max_head_vcol < 0) { - int off = virt_text_cursor_off(cts, *s == NUL); + int off = virt_text_cursor_off(csarg, *cur == NUL); if (off >= prev_rem) { if (size > off) { head += (1 + (off - prev_rem) / width) * head_mid; @@ -338,19 +274,16 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) size += added; } - if (headp != NULL) { - *headp = head; - } - + char *s = cur; colnr_T vcol_start = 0; // start from where to consider linebreak // If 'linebreak' set check at a blank before a non-blank if the line // needs a break here if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) { - char *t = cts->cts_line; + char *t = csarg->line; while (vim_isbreak((uint8_t)t[0])) { t++; } - vcol_start = (colnr_T)(t - cts->cts_line); + vcol_start = (colnr_T)(t - csarg->line); } if (wp->w_p_lbr && vcol_start <= vcol && vim_isbreak((uint8_t)s[0]) @@ -388,39 +321,50 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp) } } - return size; + return (CharSize){ .width = size, .head = head }; } -/// Like win_lbr_chartabsize(), except that we know 'linebreak' is off and -/// 'wrap' is on. This means we need to check for a double-byte character that -/// doesn't fit at the end of the screen line. +/// Like charsize_regular(), except it doesn't handle virtual text, +/// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping. +/// This function is always inlined. /// -/// @param cts -/// @param headp -/// -/// @return The number of characters take up on the screen. -static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp) +/// @see charsize_regular +/// @see charsize_fast +static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, colnr_T const vcol, + int32_t const cur_char) + FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE { - win_T *wp = cts->cts_win; - char *s = cts->cts_ptr; - colnr_T col = cts->cts_vcol; - - if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - return tabstop_padding(col, - wp->w_buffer->b_p_ts, - wp->w_buffer->b_p_vts_array); - } - int n = ptr2cells(s); - - // Add one cell for a double-width character in the last column of the - // window, displayed with a ">". - if ((n == 2) && (MB_BYTE2LEN((uint8_t)(*s)) > 1) && in_win_border(wp, col)) { - if (headp != NULL) { - *headp = 1; + // A tab gets expanded, depending on the current column + if (cur_char == TAB && use_tabstop) { + return (CharSize){ + .width = tabstop_padding(vcol, wp->w_buffer->b_p_ts, + wp->w_buffer->b_p_vts_array) + }; + } else { + int width; + if (cur_char < 0) { + width = kInvalidByteCells; + } else { + width = char2cells(cur_char); + } + + // If a double-width char doesn't fit at the end of a line, it wraps to the next line, + // and the last column displays a '>'. + if (width == 2 && cur_char >= 0x80 && wp->w_p_wrap && in_win_border(wp, vcol)) { + return (CharSize){ .width = 3, .head = 1 }; + } else { + return (CharSize){ .width = width }; } - return 3; } - return n; +} + +/// Like charsize_regular(), except it doesn't handle virtual text, +/// linebreak, breakindent and showbreak. Handles normal characters, tabs and wrapping. +/// Can be used if CSType is kCharsizeFast. +CharSize charsize_fast(CharsizeArg *csarg, colnr_T const vcol, int32_t const cur_char) + FUNC_ATTR_PURE +{ + return charsize_fast_impl(csarg->win, csarg->use_tabstop, vcol, cur_char); } /// Check that virtual column "vcol" is in the rightmost column of window "wp". @@ -451,19 +395,65 @@ static bool in_win_border(win_T *wp, colnr_T vcol) return (vcol - width1) % width2 == width2 - 1; } +/// Calculate virtual column until the given 'len'. +/// +/// @param arg Argument to charsize functions. +/// @param vcol Starting virtual column. +/// @param len First byte of the end character, or MAXCOL. +/// +/// @return virtual column before the character at 'len', +/// or full size of the line if 'len' is MAXCOL. +int linesize_regular(CharsizeArg *const csarg, int vcol, colnr_T const len) +{ + char *const line = csarg->line; + + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (ci.ptr - line < len && *ci.ptr != NUL) { + vcol += charsize_regular(csarg, ci.ptr, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + + // Check for inline virtual text after the end of the line. + if (len == MAXCOL && csarg->virt_row >= 0) { + (void)charsize_regular(csarg, ci.ptr, vcol, ci.chr.value); + vcol += csarg->cur_text_width_left + csarg->cur_text_width_right; + } + + return vcol; +} + +/// Like win_linesize_regular, but can be used when CStype is kCharsizeFast. +/// +/// @see win_linesize_regular +int linesize_fast(CharsizeArg const *const csarg, int vcol, colnr_T const len) +{ + win_T *const wp = csarg->win; + bool const use_tabstop = csarg->use_tabstop; + + char *const line = csarg->line; + + StrCharInfo ci = utf_ptr2StrCharInfo(line); + while (ci.ptr - line < len && *ci.ptr != NUL) { + vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + + return vcol; +} + /// Get how many virtual columns inline virtual text should offset the cursor. /// -/// @param cts should contain information stored by win_lbr_chartabsize() +/// @param csarg should contain information stored by charsize_regular() /// about widths of left and right gravity virtual text /// @param on_NUL whether this is the end of the line -static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) +static int virt_text_cursor_off(const CharsizeArg *csarg, bool on_NUL) { int off = 0; if (!on_NUL || !(State & MODE_NORMAL)) { - off += cts->cts_cur_text_width_left; + off += csarg->cur_text_width_left; } if (!on_NUL && (State & MODE_NORMAL)) { - off += cts->cts_cur_text_width_right; + off += csarg->cur_text_width_right; } return off; } @@ -482,115 +472,53 @@ static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL) /// @param end void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { - char *ptr; // points to current char - char *posptr; // points to char at pos->col - int incr; - int head; - colnr_T *vts = wp->w_buffer->b_p_vts_array; - int ts = (int)wp->w_buffer->b_p_ts; - - colnr_T vcol = 0; - char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line + char *const line = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line + int const end_col = pos->col; - if (pos->col == MAXCOL) { - // continue until the NUL - posptr = NULL; - } else { - // In a few cases the position can be beyond the end of the line. - for (colnr_T i = 0; i < pos->col; i++) { - if (ptr[i] == NUL) { - pos->col = i; - break; - } - } - posptr = ptr + pos->col; - posptr -= utf_head_off(line, posptr); - } - - chartabsize_T cts; + CharsizeArg csarg; bool on_NUL = false; - init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line); - cts.cts_max_head_vcol = -1; - - // This function is used very often, do some speed optimizations. - // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set - // and there are no virtual text use a simple loop. - // Also use this when 'list' is set but tabs take their normal size. - if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL)) - && !wp->w_p_lbr - && *get_showbreak_value(wp) == NUL - && !wp->w_p_bri - && !cts.cts_has_virt_text) { - while (true) { - head = 0; - int c = (uint8_t)(*ptr); + CSType const cstype = init_charsize_arg(&csarg, wp, pos->lnum, line); + csarg.max_head_vcol = -1; - // make sure we don't go past the end of the line - if (c == NUL) { - // NUL at end of line only takes one column - incr = 1; + colnr_T vcol = 0; + CharSize char_size; + StrCharInfo ci = utf_ptr2StrCharInfo(line); + if (cstype == kCharsizeFast) { + bool const use_tabstop = csarg.use_tabstop; + while (true) { + if (*ci.ptr == NUL) { + // if cursor is at NUL, it is treated like 1 cell char + char_size = (CharSize){ .width = 1 }; break; } - - // A tab gets expanded, depending on the current column - if (c == TAB) { - incr = tabstop_padding(vcol, ts, vts); - } else { - // For utf-8, if the byte is >= 0x80, need to look at - // further bytes to find the cell width. - if (c >= 0x80) { - incr = utf_ptr2cells(ptr); - } else { - incr = byte2cells(c); - } - - // If a double-cell char doesn't fit at the end of a line - // it wraps to the next line, it's like this char is three - // cells wide. - if ((incr == 2) - && wp->w_p_wrap - && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1) - && in_win_border(wp, vcol)) { - incr++; - head = 1; - } - } - - if ((posptr != NULL) && (ptr >= posptr)) { - // character at pos->col + char_size = charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value); + StrCharInfo const next = utfc_next(ci); + if (next.ptr - line > end_col) { break; } - - vcol += incr; - MB_PTR_ADV(ptr); + ci = next; + vcol += char_size.width; } } else { while (true) { - // A tab gets expanded, depending on the current column - // Other things also take up space. - head = 0; - incr = win_lbr_chartabsize(&cts, &head); - - // make sure we don't go past the end of the line - if (*cts.cts_ptr == NUL) { - // NUL at end of line only takes one column, unless there is virtual text - incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right); + char_size = charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value); + if (*ci.ptr == NUL) { + // if cursor is at NUL, it is treated like 1 cell char unless there is virtual text + char_size.width = MAX(1, csarg.cur_text_width_left + csarg.cur_text_width_right); on_NUL = true; break; } - - if ((posptr != NULL) && (cts.cts_ptr >= posptr)) { - // character at pos->col + StrCharInfo const next = utfc_next(ci); + if (next.ptr - line > end_col) { break; } - - cts.cts_vcol += incr; - MB_PTR_ADV(cts.cts_ptr); + ci = next; + vcol += char_size.width; } - vcol = cts.cts_vcol; - ptr = cts.cts_ptr; } - clear_chartabsize_arg(&cts); + + int head = char_size.head; + int incr = char_size.width; if (start != NULL) { *start = vcol + head; @@ -601,7 +529,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en } if (cursor != NULL) { - if ((*ptr == TAB) + if (ci.chr.value == TAB && (State & MODE_NORMAL) && !wp->w_p_list && !virtual_active() @@ -609,7 +537,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en // cursor at end *cursor = vcol + incr - 1; } else { - vcol += virt_text_cursor_off(&cts, on_NUL); + vcol += virt_text_cursor_off(&csarg, on_NUL); // cursor at start *cursor = vcol + head; } @@ -798,14 +726,18 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight) int plines_win_nofold(win_T *wp, linenr_T lnum) { char *s = ml_get_buf(wp->w_buffer, lnum); - chartabsize_T cts; - init_chartabsize_arg(&cts, wp, lnum, 0, s, s); - if (*s == NUL && !cts.cts_has_virt_text) { + CharsizeArg csarg; + CSType const cstype = init_charsize_arg(&csarg, wp, lnum, s); + if (*s == NUL && csarg.virt_row < 0) { return 1; // be quick for an empty line } - win_linetabsize_cts(&cts, (colnr_T)MAXCOL); - clear_chartabsize_arg(&cts); - int64_t col = cts.cts_vcol; + + int64_t col; + if (cstype == kCharsizeFast) { + col = linesize_fast(&csarg, 0, MAXCOL); + } else { + col = linesize_regular(&csarg, 0, MAXCOL); + } // If list mode is on, then the '$' at the end of the line may take up one // extra column. @@ -844,26 +776,33 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column) char *line = ml_get_buf(wp->w_buffer, lnum); - colnr_T col = 0; - chartabsize_T cts; + CharsizeArg csarg; + CSType const cstype = init_charsize_arg(&csarg, wp, lnum, line); - init_chartabsize_arg(&cts, wp, lnum, 0, line, line); - while (*cts.cts_ptr != NUL && --column >= 0) { - cts.cts_vcol += win_lbr_chartabsize(&cts, NULL); - MB_PTR_ADV(cts.cts_ptr); + colnr_T vcol = 0; + StrCharInfo ci = utf_ptr2StrCharInfo(line); + if (cstype == kCharsizeFast) { + bool const use_tabstop = csarg.use_tabstop; + while (*ci.ptr != NUL && --column >= 0) { + vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } + } else { + while (*ci.ptr != NUL && --column >= 0) { + vcol += charsize_regular(&csarg, ci.ptr, vcol, ci.chr.value).width; + ci = utfc_next(ci); + } } - // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not + // If current char is a TAB, and the TAB is not displayed as ^I, and we're not // in MODE_INSERT state, then col must be adjusted so that it represents the // last screen position of the TAB. This only fixes an error when the TAB // wraps from one screen line to the next (when 'columns' is not a multiple // of 'ts') -- webb. - col = cts.cts_vcol; - if (*cts.cts_ptr == TAB && (State & MODE_NORMAL) - && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) { - col += win_lbr_chartabsize(&cts, NULL) - 1; + colnr_T col = vcol; + if (ci.chr.value == TAB && (State & MODE_NORMAL) && csarg.use_tabstop) { + col += win_charsize(cstype, col, ci.ptr, ci.chr.value, &csarg).width - 1; } - clear_chartabsize_arg(&cts); // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc. int width = wp->w_width_inner - win_col_off(wp); diff --git a/src/nvim/plines.h b/src/nvim/plines.h index 38024e622e..a4c5ced15a 100644 --- a/src/nvim/plines.h +++ b/src/nvim/plines.h @@ -4,25 +4,96 @@ #include <stdint.h> // IWYU pragma: keep #include "nvim/marktree_defs.h" +#include "nvim/mbyte_defs.h" #include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" -/// Argument for lbr_chartabsize(). +typedef bool CSType; + +enum { + kCharsizeRegular, + kCharsizeFast, +}; + +/// Argument for char size functions. typedef struct { - win_T *cts_win; - char *cts_line; ///< start of the line - char *cts_ptr; ///< current position in line - int cts_row; + win_T *win; + char *line; ///< start of the line + + bool use_tabstop; ///< use tabstop for tab insted of counting it as ^I + int indent_width; ///< width of showbreak and breakindent on wrapped lines + /// INT_MIN if not yet calculated - bool cts_has_virt_text; ///< true if if there is inline virtual text - int cts_cur_text_width_left; ///< width of virtual text left of cursor - int cts_cur_text_width_right; ///< width of virtual text right of cursor - MarkTreeIter cts_iter[1]; + int virt_row; ///< line number, -1 if no virtual text + int cur_text_width_left; ///< width of virtual text left of cursor + int cur_text_width_right; ///< width of virtual text right of cursor - int cts_vcol; ///< virtual column at current position - int cts_max_head_vcol; ///< see win_lbr_chartabsize() -} chartabsize_T; + int max_head_vcol; ///< see charsize_regular() + MarkTreeIter iter[1]; +} CharsizeArg; + +typedef struct { + int width; + int head; // size of breakindent etc. before the character (included in width) +} CharSize; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "plines.h.generated.h" #endif + +static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr, + CharsizeArg *csarg) + REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; + +/// Get the number of cells taken up on the screen by the given character at vcol. +/// "arg->cur_text_width_left" and "arg->cur_text_width_right" are set +/// to the extra size for inline virtual text. +/// +/// When "arg->max_head_vcol" is positive, only count in "head" the size +/// of 'showbreak'/'breakindent' before "arg->max_head_vcol". +/// When "arg->max_head_vcol" is negative, only count in "head" the size +/// of 'showbreak'/'breakindent' before where cursor should be placed. +static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr, + CharsizeArg *csarg) +{ + if (cstype == kCharsizeFast) { + return charsize_fast(csarg, vcol, chr); + } else { + return charsize_regular(csarg, ptr, vcol, chr); + } +} + +static inline int linetabsize_str(char *s) + REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; + +/// Return the number of characters the string 's' will take on the screen, +/// taking into account the size of a tab. +/// +/// @param s +/// +/// @return Number of characters the string will take on the screen. +static inline int linetabsize_str(char *s) +{ + return linetabsize_col(0, s); +} + +static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) + REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE; + +/// Like linetabsize_str(), but for a given window instead of the current one. +/// +/// @param wp +/// @param line +/// @param len +/// +/// @return Number of characters the string will take on the screen. +static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len) +{ + CharsizeArg csarg; + CSType const cstype = init_charsize_arg(&csarg, wp, lnum, line); + if (cstype == kCharsizeFast) { + return linesize_fast(&csarg, 0, len); + } else { + return linesize_regular(&csarg, 0, len); + } +} diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 68e572911c..a4c95df3f1 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -53,16 +53,16 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) list(SORT NVIM_RELATIVE_SOURCES) add_custom_command( OUTPUT ${NVIM_POT} - COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + COMMAND $<TARGET_FILE:nvim_bin> -u NONE -i NONE -n --headless --cmd "set cpo+=+" -S ${CMAKE_CURRENT_SOURCE_DIR}/tojavascript.vim ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim COMMAND ${XGETTEXT_PRG} -o ${NVIM_POT} --default-domain=nvim --add-comments --keyword=_ --keyword=N_ --keyword=NGETTEXT:1,2 -D ${CMAKE_CURRENT_SOURCE_DIR} -D ${CMAKE_CURRENT_BINARY_DIR} ${NVIM_RELATIVE_SOURCES} optwin.js - COMMAND $<TARGET_FILE:nvim> -u NONE -i NONE -n --headless --cmd "set cpo+=+" + COMMAND $<TARGET_FILE:nvim_bin> -u NONE -i NONE -n --headless --cmd "set cpo+=+" -S ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim ${NVIM_POT} ../../../runtime/optwin.vim VERBATIM - DEPENDS ${NVIM_SOURCES} nvim nvim_runtime_deps) + DEPENDS ${NVIM_SOURCES} nvim_bin nvim_runtime_deps) set(LANGUAGE_MO_FILES) set(UPDATE_PO_TARGETS) @@ -88,7 +88,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) set(poFile ${CMAKE_CURRENT_SOURCE_DIR}/${name}.po) add_custom_target(check-po-${name} - COMMAND $<TARGET_FILE:nvim> -u NONE -n -e + COMMAND $<TARGET_FILE:nvim_bin> -u NONE -n -e -S ${CMAKE_CURRENT_SOURCE_DIR}/check.vim -c "if error == 0 | q | endif" -c cq ${poFile} || ${CMAKE_COMMAND} -E echo "${name}.po failed the check." @@ -182,6 +182,6 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) BuildMo(${LANGUAGE}) endforeach() - add_custom_target(translations ALL DEPENDS ${LANGUAGE_MO_FILES}) + add_custom_target(nvim_translations ALL DEPENDS ${LANGUAGE_MO_FILES}) add_custom_target(update-po DEPENDS ${UPDATE_PO_TARGETS}) endif() diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 6b8b73a2a0..f9560ce076 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1500,6 +1500,14 @@ void tui_option_set(TUIData *tui, String name, Object value) } } +void tui_chdir(TUIData *tui, String path) +{ + int err = uv_chdir(path.data); + if (err != 0) { + ELOG("Failed to chdir to %s: %s", path.data, strerror(err)); + } +} + void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, Integer endcol, Integer clearcol, Integer clearattr, LineFlags flags, const schar_T *chunk, const sattr_T *attrs) diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 8888535878..316342c028 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -384,6 +384,12 @@ void ui_attach_impl(UI *ui, uint64_t chanid) ui_refresh_options(); resettitle(); + char cwd[MAXPATHL]; + size_t cwdlen = sizeof(cwd); + if (uv_cwd(cwd, &cwdlen) == 0) { + ui_call_chdir((String){ .data = cwd, .size = cwdlen }); + } + for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) { ui_set_ext_option(ui, i, ui->ui_ext[i]); } |